diff --git a/.gitignore b/.gitignore index 9661543..c07535b 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,7 @@ Packages *.xcodeproj *.xcodeproj/xcshareddata/xcbaselines/4724F8141DA88A530003BAC6.xcbaseline Package.pins - +xcuserdata +DerrivedData/ +.swiftpm +.env diff --git a/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata b/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index 919434a..0000000 --- a/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1,7 +0,0 @@ - - - - - diff --git a/.swiftpm/xcode/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/.swiftpm/xcode/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist deleted file mode 100644 index 18d9810..0000000 --- a/.swiftpm/xcode/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +++ /dev/null @@ -1,8 +0,0 @@ - - - - - IDEDidComputeMac32BitWarning - - - diff --git a/.swiftpm/xcode/package.xcworkspace/xcuserdata/calebkleveter.xcuserdatad/UserInterfaceState.xcuserstate b/.swiftpm/xcode/package.xcworkspace/xcuserdata/calebkleveter.xcuserdatad/UserInterfaceState.xcuserstate deleted file mode 100644 index 0100a09..0000000 Binary files a/.swiftpm/xcode/package.xcworkspace/xcuserdata/calebkleveter.xcuserdatad/UserInterfaceState.xcuserstate and /dev/null differ diff --git a/.swiftpm/xcode/package.xcworkspace/xcuserdata/pro.xcuserdatad/IDEFindNavigatorScopes.plist b/.swiftpm/xcode/package.xcworkspace/xcuserdata/pro.xcuserdatad/IDEFindNavigatorScopes.plist deleted file mode 100644 index 5dd5da8..0000000 --- a/.swiftpm/xcode/package.xcworkspace/xcuserdata/pro.xcuserdatad/IDEFindNavigatorScopes.plist +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/.swiftpm/xcode/package.xcworkspace/xcuserdata/pro.xcuserdatad/UserInterfaceState.xcuserstate b/.swiftpm/xcode/package.xcworkspace/xcuserdata/pro.xcuserdatad/UserInterfaceState.xcuserstate deleted file mode 100644 index 4aaf760..0000000 Binary files a/.swiftpm/xcode/package.xcworkspace/xcuserdata/pro.xcuserdatad/UserInterfaceState.xcuserstate and /dev/null differ diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/S3DemoRun.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/S3DemoRun.xcscheme deleted file mode 100644 index 0181475..0000000 --- a/.swiftpm/xcode/xcshareddata/xcschemes/S3DemoRun.xcscheme +++ /dev/null @@ -1,100 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/S3Kit-Package.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/S3Kit-Package.xcscheme deleted file mode 100644 index c4be616..0000000 --- a/.swiftpm/xcode/xcshareddata/xcschemes/S3Kit-Package.xcscheme +++ /dev/null @@ -1,128 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/S3Kit.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/S3Kit.xcscheme deleted file mode 100644 index f206166..0000000 --- a/.swiftpm/xcode/xcshareddata/xcschemes/S3Kit.xcscheme +++ /dev/null @@ -1,67 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/S3Signer.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/S3Signer.xcscheme deleted file mode 100644 index 4c63c95..0000000 --- a/.swiftpm/xcode/xcshareddata/xcschemes/S3Signer.xcscheme +++ /dev/null @@ -1,67 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/.swiftpm/xcode/xcuserdata/calebkleveter.xcuserdatad/xcschemes/xcschememanagement.plist b/.swiftpm/xcode/xcuserdata/calebkleveter.xcuserdatad/xcschemes/xcschememanagement.plist deleted file mode 100644 index 5bb8aa3..0000000 --- a/.swiftpm/xcode/xcuserdata/calebkleveter.xcuserdatad/xcschemes/xcschememanagement.plist +++ /dev/null @@ -1,67 +0,0 @@ - - - - - SchemeUserState - - S3-Package.xcscheme_^#shared#^_ - - orderHint - 0 - - S3.xcscheme_^#shared#^_ - - orderHint - 1 - - S3DemoRun.xcscheme_^#shared#^_ - - orderHint - 4 - - S3Signer.xcscheme_^#shared#^_ - - orderHint - 2 - - S3TestTools.xcscheme_^#shared#^_ - - orderHint - 3 - - - SuppressBuildableAutocreation - - S3 - - primary - - - S3DemoApp - - primary - - - S3DemoRun - - primary - - - S3Signer - - primary - - - S3TestTools - - primary - - - S3Tests - - primary - - - - - diff --git a/.swiftpm/xcode/xcuserdata/pro.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist b/.swiftpm/xcode/xcuserdata/pro.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist deleted file mode 100644 index aa5f7f4..0000000 --- a/.swiftpm/xcode/xcuserdata/pro.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist +++ /dev/null @@ -1,56 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/.swiftpm/xcode/xcuserdata/pro.xcuserdatad/xcschemes/xcschememanagement.plist b/.swiftpm/xcode/xcuserdata/pro.xcuserdatad/xcschemes/xcschememanagement.plist deleted file mode 100644 index 0f43d6c..0000000 --- a/.swiftpm/xcode/xcuserdata/pro.xcuserdatad/xcschemes/xcschememanagement.plist +++ /dev/null @@ -1,62 +0,0 @@ - - - - - SchemeUserState - - S3DemoRun.xcscheme_^#shared#^_ - - orderHint - 3 - - S3Kit-Package.xcscheme_^#shared#^_ - - orderHint - 0 - - S3Kit.xcscheme_^#shared#^_ - - orderHint - 1 - - S3Signer.xcscheme_^#shared#^_ - - orderHint - 2 - - S3TestTools.xcscheme_^#shared#^_ - - orderHint - 3 - - - SuppressBuildableAutocreation - - S3 - - primary - - - S3DemoRun - - primary - - - S3Kit - - primary - - - S3Provider - - primary - - - S3Signer - - primary - - - - - diff --git a/Package.resolved b/Package.resolved index 0118a55..c156a44 100644 --- a/Package.resolved +++ b/Package.resolved @@ -6,8 +6,8 @@ "repositoryURL": "https://github.com/swift-server/async-http-client.git", "state": { "branch": null, - "revision": "64851a1a0a2a9e8fa7ae7b3508ce46a1da4a2e1d", - "version": "1.0.0-alpha.2" + "revision": "e2636a4c24e646d3e480fc666da0c090818beb09", + "version": "1.1.0" } }, { @@ -15,8 +15,8 @@ "repositoryURL": "https://github.com/vapor/async-kit.git", "state": { "branch": null, - "revision": "b5742bfbbe2d60f3b77465a0907777261e418f23", - "version": "1.0.0-alpha.1" + "revision": "1f145cffe2109d0fc420bc03b31dacf9de2c7573", + "version": "1.0.0" } }, { @@ -24,8 +24,8 @@ "repositoryURL": "https://github.com/vapor/console-kit.git", "state": { "branch": null, - "revision": "58d000a6236df517e84f162d21afeca757afc2d1", - "version": "4.0.0-alpha.2" + "revision": "f674ff0d037346ad5f9a4feb5088b769cdb9e5d3", + "version": "4.0.0" } }, { @@ -38,21 +38,30 @@ } }, { - "package": "open-crypto", - "repositoryURL": "https://github.com/vapor/open-crypto.git", + "package": "routing-kit", + "repositoryURL": "https://github.com/vapor/routing-kit.git", "state": { "branch": null, - "revision": "06d26edb8e28295bb7103b4f950d5ea58d634c1b", - "version": "4.0.0-alpha.2" + "revision": "35da702471cbcc308f58d38aa8fea971fa3fb166", + "version": "4.0.0" } }, { - "package": "routing-kit", - "repositoryURL": "https://github.com/vapor/routing-kit.git", + "package": "swift-backtrace", + "repositoryURL": "https://github.com/swift-server/swift-backtrace.git", + "state": { + "branch": null, + "revision": "f2fd8c4845a123419c348e0bc4b3839c414077d5", + "version": "1.2.0" + } + }, + { + "package": "swift-crypto", + "repositoryURL": "https://github.com/apple/swift-crypto.git", "state": { "branch": null, - "revision": "6c7f4b471f9662d05045d82e64e22d5572a16a82", - "version": "4.0.0-alpha.1" + "revision": "d67ac68d09a95443303e9d6e37b34e7ba101d5f1", + "version": "1.0.1" } }, { @@ -60,8 +69,17 @@ "repositoryURL": "https://github.com/apple/swift-log.git", "state": { "branch": null, - "revision": "e8aabbe95db22e064ad42f1a4a9f8982664c70ed", - "version": "1.1.1" + "revision": "74d7b91ceebc85daf387ebb206003f78813f71aa", + "version": "1.2.0" + } + }, + { + "package": "swift-metrics", + "repositoryURL": "https://github.com/apple/swift-metrics.git", + "state": { + "branch": null, + "revision": "708b960b4605abb20bc55d65abf6bad607252200", + "version": "2.0.0" } }, { @@ -69,8 +87,8 @@ "repositoryURL": "https://github.com/apple/swift-nio.git", "state": { "branch": null, - "revision": "32760eae40e6b7cb81d4d543bb0a9f548356d9a2", - "version": "2.7.1" + "revision": "a27a07719ca785bcaca019a5b9fe1814b981b4a2", + "version": "2.15.0" } }, { @@ -78,8 +96,8 @@ "repositoryURL": "https://github.com/apple/swift-nio-extras.git", "state": { "branch": null, - "revision": "66f9a509ed3cc56b6eb367515e421beca4a0af53", - "version": "1.2.0" + "revision": "b4dbfacff47fb8d0f9e0a422d8d37935a9f10570", + "version": "1.4.0" } }, { @@ -87,8 +105,8 @@ "repositoryURL": "https://github.com/apple/swift-nio-http2.git", "state": { "branch": null, - "revision": "86ce1dcd0df501401eb1a0d445dbd90aaad84a64", - "version": "1.5.0" + "revision": "82eb3fa0974b838358ee46bc6c5381e5ae5de6b9", + "version": "1.11.0" } }, { @@ -96,8 +114,8 @@ "repositoryURL": "https://github.com/apple/swift-nio-ssl.git", "state": { "branch": null, - "revision": "f5dd7a60ff56f501ff7bf9be753e4b1875bfaf20", - "version": "2.4.0" + "revision": "ae213938e151964aa691f0e902462fbe06baeeb6", + "version": "2.7.1" } }, { @@ -105,8 +123,8 @@ "repositoryURL": "https://github.com/vapor/vapor.git", "state": { "branch": null, - "revision": "35f8dc3df0976cff76945a03bfcb763e46a16440", - "version": "4.0.0-alpha.3.1" + "revision": "ad2aeccfbdd1275c0a8817a8024f0b98d65e3880", + "version": "4.0.0-rc.3.12" } }, { @@ -118,13 +136,22 @@ "version": "0.0.1" } }, + { + "package": "websocket-kit", + "repositoryURL": "https://github.com/vapor/websocket-kit.git", + "state": { + "branch": null, + "revision": "aac462481bcd3039d5db9a0fbe7f4e47eefe2295", + "version": "2.0.0-rc.1" + } + }, { "package": "XMLCoding", "repositoryURL": "https://github.com/LiveUI/XMLCoding.git", "state": { "branch": null, - "revision": "8c760e960a5e53a5338c2871c4fcdf06b8c5ace4", - "version": "0.4.0" + "revision": "f0fbfe17e73f329e13a6133ff5437f7b174049fd", + "version": "0.4.1" } } ] diff --git a/Package.swift b/Package.swift index 9f5861b..176eb09 100644 --- a/Package.swift +++ b/Package.swift @@ -1,62 +1,65 @@ -// swift-tools-version:4.0 +// swift-tools-version:5.2 import PackageDescription let package = Package( name: "S3Kit", + platforms: [ + .macOS(.v10_15) + ], products: [ .library(name: "S3Kit", targets: ["S3Kit"]), .library(name: "S3Signer", targets: ["S3Signer"]), -// .library(name: "S3TestTools", targets: ["S3TestTools"]) + // .library(name: "S3TestTools", targets: ["S3TestTools"]) ], dependencies: [ - .package(url: "https://github.com/apple/swift-nio.git", from: "2.5.0"), - .package(url: "https://github.com/vapor/vapor.git", from: "4.0.0-alpha.3"), - .package(url: "https://github.com/vapor/open-crypto.git", from: "4.0.0-alpha.2"), - .package(url: "https://github.com/swift-server/async-http-client.git", from: "1.0.0-alpha.2"), + .package(url: "https://github.com/vapor/vapor.git", from: "4.0.0-rc.3.11"), + .package(url: "https://github.com/apple/swift-nio.git", from: "2.13.1"), + .package(url: "https://github.com/apple/swift-crypto.git", from: "1.0.0"), + .package(url: "https://github.com/swift-server/async-http-client.git", from: "1.0.0"), .package(url: "https://github.com/Einstore/HTTPMediaTypes.git", from: "0.0.1"), - .package(url: "https://github.com/Einstore/WebErrorKit.git", from: "0.0.1"), + .package(name: "WebError", url: "https://github.com/Einstore/WebErrorKit.git", from: "0.0.1"), .package(url: "https://github.com/LiveUI/XMLCoding.git", from: "0.1.0") ], targets: [ .target( name: "S3Kit", dependencies: [ - "S3Signer", - "AsyncHTTPClient", - "HTTPMediaTypes", - "XMLCoding" + .target(name: "S3Signer"), + .product(name: "AsyncHTTPClient", package: "async-http-client"), + .product(name: "XMLCoding", package: "XMLCoding"), + .product(name: "HTTPMediaTypes", package: "HTTPMediaTypes") ] ), .target( - name: "S3Provider", + name: "S3", dependencies: [ - "Vapor", - "S3Kit" + .target(name: "S3Kit"), + .product(name: "Vapor", package: "vapor") ] ), .target( name: "S3DemoRun", dependencies: [ - "Vapor", - "S3Provider" + .target(name: "S3"), + .product(name: "Vapor", package: "vapor") ] ), .target( name: "S3Signer", dependencies: [ - "OpenCrypto", - "NIOHTTP1", - "HTTPMediaTypes", - "WebErrorKit" + .product(name: "Crypto", package: "swift-crypto"), + .product(name: "HTTPMediaTypes", package: "HTTPMediaTypes"), + .product(name: "WebErrorKit", package: "WebError"), + .product(name: "NIOHTTP1", package: "swift-nio"), ] ), -// .target(name: "S3TestTools", dependencies: [ -// "Vapor", -// "S3Kit" -// ] -// ), + // .target(name: "S3TestTools", dependencies: [ + // "Vapor", + // "S3Kit" + // ] + // ), .testTarget(name: "S3Tests", dependencies: [ - "S3Kit" + .target(name: "S3Kit") ] ) ] diff --git a/README.md b/README.md index 9297b7b..fbeae2c 100644 --- a/README.md +++ b/README.md @@ -26,50 +26,30 @@ Update dependencies and targets in Package.swift ```swift dependencies: [ ... - .package(url: "https://github.com/LiveUI/S3.git", from: "3.0.0-RC3.2"), + .package(url: "https://github.com/LiveUI/S3.git", from: "4.0.0-rc.1"), ], targets: [ - .target(name: "App", dependencies: ["Vapor", "S3"]), + .target(name: "App", dependencies: [ + .package(name: "S3", package: "S3Kit") + ], ... ] ``` -Run ```vapor update``` - -Register S3Client as a service in your configure method +Configure S3 in your `configure` method: ```swift -try services.register(s3: S3Signer.Config(...), defaultBucket: "my-bucket") -``` - -to use a custom Minio server, use this Config/Region: - -``` -S3Signer.Config(accessKey: accessKey, - secretKey: secretKey, - region: Region(name: RegionName.usEast1, - hostName: "127.0.0.1:9000", - useTLS: false) +app.s3.configuration = .init(accessKey: "", secretKey: "", region: Region.euNorth1, defaultBucket: "my-bucket") ``` -use S3Client +Using S3 inside your route handlers ```swift import S3 -let s3 = try req.makeS3Client() // or req.make(S3Client.self) as? S3 -s3.put(...) -s3.get(...) -s3.delete(...) -``` - -if you only want to use the signer - -```swift -import S3Signer - -let s3 = try req.makeS3Signer() // or req.make(S3Signer.self) -s3.headers(...) +app.get("buckets") { req -> EventLoopFuture in + req.s3.buckets() +} ``` ### Available methods @@ -82,178 +62,166 @@ public protocol S3Client: Service { func buckets(on: Container) -> EventLoopFuture /// Create a bucket - func create(bucket: String, region: Region?, on container: Container) -> EventLoopFuture + func create(bucket: String, region: Region?) -> EventLoopFuture /// Delete a bucket - func delete(bucket: String, region: Region?, on container: Container) -> EventLoopFuture + func delete(bucket: String, region: Region?) -> EventLoopFuture /// Get bucket location - func location(bucket: String, on container: Container) -> EventLoopFuture + func location(bucket: String) -> EventLoopFuture /// Get list of objects - func list(bucket: String, region: Region?, on container: Container) -> EventLoopFuture + func list(bucket: String, region: Region?) -> EventLoopFuture /// Get list of objects - func list(bucket: String, region: Region?, headers: [String: String], on container: Container) -> EventLoopFuture + func list(bucket: String, region: Region?, headers: [String: String]) -> EventLoopFuture /// Upload file to S3 - func put(file: File.Upload, headers: [String: String], on: Container) throws -> EventLoopEventLoopFuture + func put(file: File.Upload, headers: [String: String]) throws -> EventLoopEventLoopFuture /// Upload file to S3 - func put(file url: URL, destination: String, access: AccessControlList, on: Container) -> EventLoopFuture + func put(file url: URL, destination: String, access: AccessControlList) -> EventLoopFuture /// Upload file to S3 - func put(file url: URL, destination: String, bucket: String?, access: AccessControlList, on: Container) -> EventLoopFuture + func put(file url: URL, destination: String, bucket: String?, access: AccessControlList) -> EventLoopFuture /// Upload file to S3 - func put(file path: String, destination: String, access: AccessControlList, on: Container) -> EventLoopFuture + func put(file path: String, destination: String, access: AccessControlList) -> EventLoopFuture /// Upload file to S3 - func put(file path: String, destination: String, bucket: String?, access: AccessControlList, on: Container) -> EventLoopFuture + func put(file path: String, destination: String, bucket: String?, access: AccessControlList) -> EventLoopFuture /// Upload file to S3 - func put(string: String, destination: String, on: Container) -> EventLoopFuture + func put(string: String, destination: String) -> EventLoopFuture /// Upload file to S3 - func put(string: String, destination: String, access: AccessControlList, on: Container) -> EventLoopFuture + func put(string: String, destination: String, access: AccessControlList) -> EventLoopFuture /// Upload file to S3 - func put(string: String, mime: MediaType, destination: String, on: Container) -> EventLoopFuture + func put(string: String, mime: MediaType, destination: String) -> EventLoopFuture /// Upload file to S3 - func put(string: String, mime: MediaType, destination: String, access: AccessControlList, on: Container) -> EventLoopFuture + func put(string: String, mime: MediaType, destination: String, access: AccessControlList) -> EventLoopFuture /// Upload file to S3 - func put(string: String, mime: MediaType, destination: String, bucket: String?, access: AccessControlList, on: Container) -> EventLoopFuture + func put(string: String, mime: MediaType, destination: String, bucket: String?, access: AccessControlList) -> EventLoopFuture /// Retrieve file data from S3 - func get(fileInfo file: LocationConvertible, on container: Container) -> EventLoopFuture + func get(fileInfo file: LocationConvertible) -> EventLoopFuture /// Retrieve file data from S3 - func get(fileInfo file: LocationConvertible, headers: [String: String], on container: Container) -> EventLoopFuture + func get(fileInfo file: LocationConvertible, headers: [String: String]) -> EventLoopFuture /// Retrieve file data from S3 - func get(file: LocationConvertible, on: Container) -> EventLoopFuture + func get(file: LocationConvertible) -> EventLoopFuture /// Retrieve file data from S3 - func get(file: LocationConvertible, headers: [String: String], on: Container) -> EventLoopFuture + func get(file: LocationConvertible, headers: [String: String]) -> EventLoopFuture /// Delete file from S3 - func delete(file: LocationConvertible, on: Container) -> EventLoopFuture + func delete(file: LocationConvertible) -> EventLoopFuture /// Delete file from S3 - func delete(file: LocationConvertible, headers: [String: String], on: Container) -> EventLoopFuture + func delete(file: LocationConvertible, headers: [String: String]) -> EventLoopFuture } ``` ### Example usage ```swift -public func routes(_ router: Router) throws { - +public func routes(_ app: Application) throws { // Get all available buckets - router.get("buckets") { req -> EventLoopFuture in - let s3 = try req.makeS3Client() - return try s3.buckets(on: req) + app.get("buckets") { req -> EventLoopFuture in + req.s3.buckets() } // Create new bucket - router.put("bucket") { req -> EventLoopFuture in - let s3 = try req.makeS3Client() - return try s3.create(bucket: "api-created-bucket", region: .euCentral1, on: req).map(to: String.self) { + app.put("bucket") { req -> EventLoopFuture in + return req.s3.create(bucket: "api-created-bucket", region: .euCentral1).map { return ":)" - }.catchMap({ (error) -> (String) in - if let error = error.s3ErrorMessage() { - return error.message - } - return ":(" - } - ) - } - - // Locate bucket (get region) - router.get("bucket/location") { req -> EventLoopFuture in - let s3 = try req.makeS3Client() - return try s3.location(bucket: "bucket-name", on: req).map(to: String.self) { region in - return region.hostUrlString() - }.catchMap({ (error) -> (String) in - if let error = error as? S3.Error { - switch error { - case .errorResponse(_, let error): - return error.message - default: - return "S3 :(" - } - } - return ":(" + }.recover { error in + if let error = error.s3ErrorMessage() { + return error.message } - ) + return ":(" + } } + // Delete bucket - router.delete("bucket") { req -> EventLoopFuture in - let s3 = try req.makeS3Client() - return try s3.delete(bucket: "api-created-bucket", region: .euCentral1, on: req).map(to: String.self) { + app.delete("bucket") { req -> EventLoopFuture in + return req.s3.delete(bucket: "api-created-bucket", region: .euCentral1).map { return ":)" - }.catchMap({ (error) -> (String) in - if let error = error.s3ErrorMessage() { - return error.message - } - return ":(" - } - ) + }.recover { error in + if let error = error.s3ErrorMessage() { + return error.message + } + return ":(" + } } - - // Get list of objects - router.get("files") { req -> EventLoopFuture in - let s3 = try req.makeS3Client() - return try s3.list(bucket: "booststore", region: .usEast1, headers: [:], on: req).catchMap({ (error) -> (BucketResults) in + + // List files + app.get("files") { req -> EventLoopFuture in + return req.s3.list(bucket: DEFAULT_BUCKET, region: .euCentral1, headers: [:]).flatMapErrorThrowing { error in if let error = error.s3ErrorMessage() { print(error.message) } + throw error - }) + } } - + + // Bucket location + app.get("bucket", "location") { req -> EventLoopFuture in + return req.s3.location(bucket: DEFAULT_BUCKET).map { region in + return region.hostUrlString() + }.recover { error -> String in + if let error = error as? S3.Error { + switch error { + case .errorResponse(_, let error): + return error.message + default: + return "S3 :(" + } + } + return ":(" + } + } + // Demonstrate work with files - router.get("files/test") { req -> EventLoopFuture in + app.get("files", "test") { req -> EventLoopFuture in let string = "Content of my example file" - + let fileName = "file-hu.txt" - - let s3 = try req.makeS3Client() - do { - // Upload a file from string - return try s3.put(string: string, destination: fileName, access: .publicRead, on: req).flatMap(to: String.self) { putResponse in - print("PUT response:") - print(putResponse) - // Get the content of the newly uploaded file - return try s3.get(file: fileName, on: req).flatMap(to: String.self) { getResponse in - print("GET response:") - print(getResponse) - print(String(data: getResponse.data, encoding: .utf8) ?? "Unknown content!") - // Get info about the file (HEAD) - return try s3.get(fileInfo: fileName, on: req).flatMap(to: String.self) { infoResponse in - print("HEAD/Info response:") - print(infoResponse) - // Delete the file - return try s3.delete(file: fileName, on: req).map() { response in - print("DELETE response:") - print(response) - let json = try JSONEncoder().encode(infoResponse) - return String(data: json, encoding: .utf8) ?? "Unknown content!" - }.catchMap({ error -> (String) in - if let error = error.s3ErrorMessage() { - return error.message - } - return ":(" - } - ) + return req.s3.put(string: string, destination: fileName, access: .publicRead).flatMap { putResponse -> EventLoopFuture in + print("PUT response:") + print(putResponse) + return req.s3.get(file: fileName).flatMap { getResponse in + print("GET response:") + print(getResponse) + print(String(data: getResponse.data, encoding: .utf8) ?? "Unknown content!") + + return req.s3.get(fileInfo: fileName).flatMap { infoResponse in + print("HEAD/Info response:") + print(infoResponse) + + return req.s3.delete(file: fileName).flatMapThrowing { response in + print("DELETE response:") + print(response) + let json = try JSONEncoder().encode(infoResponse) + return String(data: json, encoding: .utf8) ?? "Unknown content!" + }.recover { error -> (String) in + if let error = error.s3ErrorMessage() { + return error.message + } + return ":(" } } } - } catch { - print(error) - fatalError() + }.recover { error -> (String) in + if let error = error.s3ErrorMessage() { + return error.message + } + return ":(" } } } diff --git a/Sources/S3/Application+S3.swift b/Sources/S3/Application+S3.swift new file mode 100644 index 0000000..b18230f --- /dev/null +++ b/Sources/S3/Application+S3.swift @@ -0,0 +1,23 @@ +import Vapor +import S3Signer + +extension Application { + public struct S3 { + fileprivate let application: Application + + struct ConfigurationKey: StorageKey { + typealias Value = S3Signer.Config + } + + public var configuration: S3Signer.Config? { + get { + application.storage[ConfigurationKey.self] + } + nonmutating set { + application.storage[ConfigurationKey.self] = newValue + } + } + } + + public var s3: S3 { .init(application: self) } +} diff --git a/Sources/S3Provider/Exports.swift b/Sources/S3/Exports.swift similarity index 100% rename from Sources/S3Provider/Exports.swift rename to Sources/S3/Exports.swift diff --git a/Sources/S3Provider/Model/Models+Content.swift b/Sources/S3/Model/Models+Content.swift similarity index 100% rename from Sources/S3Provider/Model/Models+Content.swift rename to Sources/S3/Model/Models+Content.swift diff --git a/Sources/S3/Request+S3.swift b/Sources/S3/Request+S3.swift new file mode 100644 index 0000000..e9c4056 --- /dev/null +++ b/Sources/S3/Request+S3.swift @@ -0,0 +1,12 @@ +import Vapor +import S3Kit + +extension Request { + public var s3: S3 { + guard let config = application.s3.configuration else { + fatalError("S3 is not configured, please use application.s3.configuration = ...") + } + + return .init(config: config, eventLoop: eventLoop, httpClient: application.client.http) + } +} diff --git a/Sources/S3DemoRun/main.swift b/Sources/S3DemoRun/main.swift index bea445b..ba213b9 100644 --- a/Sources/S3DemoRun/main.swift +++ b/Sources/S3DemoRun/main.swift @@ -1,26 +1,23 @@ import Vapor -import S3Provider +import S3 +let DEFAULT_BUCKET = "test-bucket-s3-vapor" -let DEFAULT_BUCKET = "s3-lib-test.einstore.mgw.cz" - - -func routes(_ router: Routes, _ c: Container) throws { +func routes(_ app: Application) throws { guard let key = Environment.get("S3_ACCESS_KEY"), let secret = Environment.get("S3_SECRET") else { fatalError("Missing AWS API key/secret") } - let config = S3Signer.Config(accessKey: key, secretKey: secret, region: Region.euCentral1) - let s3: S3Client = try S3(defaultBucket: DEFAULT_BUCKET, config: config) + app.s3.configuration = .init(accessKey: key, secretKey: secret, region: Region.euNorth1, defaultBucket: DEFAULT_BUCKET) // Get all available buckets - router.get("buckets") { req -> EventLoopFuture in - return s3.buckets(on: req.eventLoop) + app.get("buckets") { req -> EventLoopFuture in + req.s3.buckets() } // Create new bucket - router.put("bucket") { req -> EventLoopFuture in - return s3.create(bucket: "api-created-bucket", region: .euCentral1, on: req.eventLoop).map { + app.put("bucket") { req -> EventLoopFuture in + return req.s3.create(bucket: "api-created-bucket", region: .euCentral1).map { return ":)" }.recover { error in if let error = error.s3ErrorMessage() { @@ -29,10 +26,10 @@ func routes(_ router: Routes, _ c: Container) throws { return ":(" } } - + // Delete bucket - router.delete("bucket") { req -> EventLoopFuture in - return s3.delete(bucket: "api-created-bucket", region: .euCentral1, on: req.eventLoop).map { + app.delete("bucket") { req -> EventLoopFuture in + return req.s3.delete(bucket: "api-created-bucket", region: .euCentral1).map { return ":)" }.recover { error in if let error = error.s3ErrorMessage() { @@ -41,10 +38,10 @@ func routes(_ router: Routes, _ c: Container) throws { return ":(" } } - - // Delete bucket - router.get("files") { req -> EventLoopFuture in - return s3.list(bucket: DEFAULT_BUCKET, region: .euCentral1, headers: [:], on: req.eventLoop).flatMapErrorThrowing { error in + + // List files + app.get("files") { req -> EventLoopFuture in + return req.s3.list(bucket: DEFAULT_BUCKET, region: .euCentral1, headers: [:]).flatMapErrorThrowing { error in if let error = error.s3ErrorMessage() { print(error.message) } @@ -52,10 +49,10 @@ func routes(_ router: Routes, _ c: Container) throws { throw error } } - + // Bucket location - router.get("bucket", "location") { req -> EventLoopFuture in - return s3.location(bucket: DEFAULT_BUCKET, on: req.eventLoop).map { region in + app.get("bucket", "location") { req -> EventLoopFuture in + return req.s3.location(bucket: DEFAULT_BUCKET).map { region in return region.hostUrlString() }.recover { error -> String in if let error = error as? S3.Error { @@ -69,25 +66,25 @@ func routes(_ router: Routes, _ c: Container) throws { return ":(" } } - + // Demonstrate work with files - router.get("files", "test") { req -> EventLoopFuture in + app.get("files", "test") { req -> EventLoopFuture in let string = "Content of my example file" - + let fileName = "file-hu.txt" - return s3.put(string: string, destination: fileName, access: .publicRead, on: req.eventLoop).flatMap { putResponse -> EventLoopFuture in + return req.s3.put(string: string, destination: fileName, access: .publicRead).flatMap { putResponse -> EventLoopFuture in print("PUT response:") print(putResponse) - return s3.get(file: fileName, on: req.eventLoop).flatMap { getResponse in + return req.s3.get(file: fileName).flatMap { getResponse in print("GET response:") print(getResponse) print(String(data: getResponse.data, encoding: .utf8) ?? "Unknown content!") - return s3.get(fileInfo: fileName, on: req.eventLoop).flatMap { infoResponse in + return req.s3.get(fileInfo: fileName).flatMap { infoResponse in print("HEAD/Info response:") print(infoResponse) - return s3.delete(file: fileName, on: req.eventLoop).flatMapThrowing { response in + return req.s3.delete(file: fileName).flatMapThrowing { response in print("DELETE response:") print(response) let json = try JSONEncoder().encode(infoResponse) @@ -110,38 +107,13 @@ func routes(_ router: Routes, _ c: Container) throws { } /// Called before your application initializes. -func configure(_ s: inout Services) throws { - /// Register routes - s.extend(Routes.self) { r, c in - try routes(r, c) - } - - /// Register middleware - s.register(MiddlewareConfiguration.self) { c in - // Create _empty_ middleware config - var middlewares = MiddlewareConfiguration() - - // Serves files from `Public/` directory - /// middlewares.use(FileMiddleware.self) - - // Catches errors and converts to HTTP response - try middlewares.use(c.make(ErrorMiddleware.self)) - - return middlewares - } -} - -func boot(_ app: Application) throws { - try LoggingSystem.bootstrap(from: &app.environment) - try app.boot() -} - -public func app(_ environment: Environment) throws -> Application { - let app = Application.init(environment: environment) { s in - try configure(&s) - } - try boot(app) - return app +func configure(_ app: Application) throws { + try routes(app) } -try app(.detect()).run() +var env = try Environment.detect() +try LoggingSystem.bootstrap(from: &env) +let app = Application(env) +defer { app.shutdown() } +try configure(app) +try app.run() diff --git a/Sources/S3Kit/Extensions/S3+Bucket.swift b/Sources/S3Kit/Extensions/S3+Bucket.swift index 47c461e..6934b3b 100644 --- a/Sources/S3Kit/Extensions/S3+Bucket.swift +++ b/Sources/S3Kit/Extensions/S3+Bucket.swift @@ -9,7 +9,7 @@ extension S3 { // MARK: Buckets /// Get bucket location - public func location(bucket: String, on eventLoop: EventLoop) -> EventLoopFuture { + public func location(bucket: String) -> EventLoopFuture { let url: URL let awsHeaders: HTTPHeaders let region = Region.euWest2 @@ -21,7 +21,7 @@ extension S3 { return eventLoop.makeFailedFuture(error) } - return make(request: url, method: .GET, headers: awsHeaders, data: Data(), on: eventLoop).flatMapThrowing { response in + return make(request: url, method: .GET, headers: awsHeaders, data: Data()).flatMapThrowing { response in if response.status == .notFound { throw Error.notFound } @@ -48,7 +48,7 @@ extension S3 { } /// Delete bucket - public func delete(bucket: String, region: Region? = nil, on eventLoop: EventLoop) -> EventLoopFuture { + public func delete(bucket: String, region: Region? = nil) -> EventLoopFuture { let url: URL let awsHeaders: HTTPHeaders @@ -59,13 +59,13 @@ extension S3 { return eventLoop.makeFailedFuture(error) } - return make(request: url, method: .DELETE, headers: awsHeaders, data: Data(), on: eventLoop).flatMapThrowing(self.check).map { _ in + return make(request: url, method: .DELETE, headers: awsHeaders, data: Data()).flatMapThrowing(self.check).map { _ in return Void() } } /// Create a bucket - public func create(bucket: String, region: Region? = nil, on eventLoop: EventLoop) -> EventLoopFuture { + public func create(bucket: String, region: Region? = nil) -> EventLoopFuture { let region = region ?? signer.config.region let content = """ @@ -84,7 +84,7 @@ extension S3 { return eventLoop.makeFailedFuture(error) } - return make(request: url, method: .PUT, headers: awsHeaders, data: data, on: eventLoop).flatMapThrowing(self.check).map { _ in + return make(request: url, method: .PUT, headers: awsHeaders, data: data).flatMapThrowing(self.check).map { _ in return Void() } } diff --git a/Sources/S3Kit/Extensions/S3+Copy.swift b/Sources/S3Kit/Extensions/S3+Copy.swift index dc0200a..270dbf1 100644 --- a/Sources/S3Kit/Extensions/S3+Copy.swift +++ b/Sources/S3Kit/Extensions/S3+Copy.swift @@ -8,7 +8,7 @@ extension S3 { // MARK: Copy /// Copy file on S3 - public func copy(file: LocationConvertible, to: LocationConvertible, headers strHeaders: [String: String], on eventLoop: EventLoop) -> EventLoopFuture { + public func copy(file: LocationConvertible, to: LocationConvertible, headers strHeaders: [String: String]) -> EventLoopFuture { do { let destinationUrl = try makeURLBuilder().url(file: to) @@ -21,7 +21,7 @@ extension S3 { payload: .none ) - return make(request: destinationUrl, method: .PUT, headers: headers, on: eventLoop).flatMapThrowing { response in + return make(request: destinationUrl, method: .PUT, headers: headers).flatMapThrowing { response in return try self.check(response).decode(to: File.CopyResponse.self) } } catch let error { diff --git a/Sources/S3Kit/Extensions/S3+Delete.swift b/Sources/S3Kit/Extensions/S3+Delete.swift index 8a6df8b..7f90e1e 100644 --- a/Sources/S3Kit/Extensions/S3+Delete.swift +++ b/Sources/S3Kit/Extensions/S3+Delete.swift @@ -8,7 +8,7 @@ extension S3 { // MARK: Delete /// Delete file from S3 - public func delete(file: LocationConvertible, headers strHeaders: [String: String], on eventLoop: EventLoop) -> EventLoopFuture { + public func delete(file: LocationConvertible, headers strHeaders: [String: String]) -> EventLoopFuture { let headers: HTTPHeaders let url: URL @@ -19,15 +19,15 @@ extension S3 { return eventLoop.makeFailedFuture(error) } - return make(request: url, method: .DELETE, headers: headers, data: nil, on: eventLoop).flatMapThrowing(self.check).map { _ in + return make(request: url, method: .DELETE, headers: headers, data: nil).flatMapThrowing(self.check).map { _ in return Void() } } /// Delete file from S3 - public func delete(file: LocationConvertible, on eventLoop: EventLoop) -> EventLoopFuture { + public func delete(file: LocationConvertible) -> EventLoopFuture { return - delete(file: file, headers: [:], on: eventLoop) + delete(file: file, headers: [:]) } } diff --git a/Sources/S3Kit/Extensions/S3+Get.swift b/Sources/S3Kit/Extensions/S3+Get.swift index 3cc2ada..9c64125 100644 --- a/Sources/S3Kit/Extensions/S3+Get.swift +++ b/Sources/S3Kit/Extensions/S3+Get.swift @@ -16,7 +16,7 @@ extension S3 { // MARK: Get /// Retrieve file data from S3 - public func get(file: LocationConvertible, headers strHeaders: [String: String], on eventLoop: EventLoop) -> EventLoopFuture { + public func get(file: LocationConvertible, headers strHeaders: [String: String]) -> EventLoopFuture { let url: URL let headers: HTTPHeaders @@ -27,7 +27,7 @@ extension S3 { return eventLoop.makeFailedFuture(error) } - return make(request: url, method: .GET, headers: headers, on: eventLoop).flatMapThrowing { response in + return make(request: url, method: .GET, headers: headers).flatMapThrowing { response in try self.check(response) guard var b = response.body, let data = b.readBytes(length: b.readableBytes) else { @@ -40,8 +40,8 @@ extension S3 { } /// Retrieve file data from S3 - public func get(file: LocationConvertible, on eventLoop: EventLoop) -> EventLoopFuture { - return get(file: file, headers: [:], on: eventLoop) + public func get(file: LocationConvertible) -> EventLoopFuture { + return get(file: file, headers: [:]) } } diff --git a/Sources/S3Kit/Extensions/S3+List.swift b/Sources/S3Kit/Extensions/S3+List.swift index 07abee5..af913da 100644 --- a/Sources/S3Kit/Extensions/S3+List.swift +++ b/Sources/S3Kit/Extensions/S3+List.swift @@ -7,7 +7,7 @@ import NIOHTTP1 extension S3 { /// Get list of objects - public func list(bucket: String, region: Region? = nil, headers: [String: String], on eventLoop: EventLoop) -> EventLoopFuture { + public func list(bucket: String, region: Region? = nil, headers: [String: String]) -> EventLoopFuture { let region = region ?? signer.config.region guard let baseUrl = URL(string: region.hostUrlString(bucket: bucket)), let host = baseUrl.host, var components = URLComponents(string: baseUrl.absoluteString) else { @@ -30,15 +30,15 @@ extension S3 { return eventLoop.makeFailedFuture(error) } - return make(request: url, method: .GET, headers: awsHeaders, data: Data(), on: eventLoop).flatMapThrowing { response in + return make(request: url, method: .GET, headers: awsHeaders, data: Data()).flatMapThrowing { response in try self.check(response) return try response.decode(to: BucketResults.self) } } /// Get list of objects - public func list(bucket: String, region: Region? = nil, on eventLoop: EventLoop) -> EventLoopFuture { - return list(bucket: bucket, region: region, headers: [:], on: eventLoop) + public func list(bucket: String, region: Region? = nil) -> EventLoopFuture { + return list(bucket: bucket, region: region, headers: [:]) } } diff --git a/Sources/S3Kit/Extensions/S3+Move.swift b/Sources/S3Kit/Extensions/S3+Move.swift index 3801b02..b626364 100644 --- a/Sources/S3Kit/Extensions/S3+Move.swift +++ b/Sources/S3Kit/Extensions/S3+Move.swift @@ -7,9 +7,9 @@ extension S3 { // MARK: Move /// Copy file on S3 - public func move(file: LocationConvertible, to destination: LocationConvertible, headers: [String: String], on eventLoop: EventLoop) -> EventLoopFuture { - return copy(file: file, to: destination, headers: headers, on: eventLoop).flatMap { copyResult in - return self.delete(file: file, on: eventLoop).map { _ in + public func move(file: LocationConvertible, to destination: LocationConvertible, headers: [String: String]) -> EventLoopFuture { + return copy(file: file, to: destination, headers: headers).flatMap { copyResult in + return self.delete(file: file).map { _ in return copyResult } } diff --git a/Sources/S3Kit/Extensions/S3+ObjectInfo.swift b/Sources/S3Kit/Extensions/S3+ObjectInfo.swift index 1b9f91f..aa36b62 100644 --- a/Sources/S3Kit/Extensions/S3+ObjectInfo.swift +++ b/Sources/S3Kit/Extensions/S3+ObjectInfo.swift @@ -9,19 +9,19 @@ extension S3 { /// Get acl file information (ACL) /// https://docs.aws.amazon.com/AmazonS3/latest/API/RESTObjectGETacl.html - public func get(acl file: LocationConvertible, headers: [String: String], on eventLoop: EventLoop) -> EventLoopFuture { + public func get(acl file: LocationConvertible, headers: [String: String]) -> EventLoopFuture { fatalError("Not implemented") } /// Get acl file information /// https://docs.aws.amazon.com/AmazonS3/latest/API/RESTObjectGETacl.html - func get(acl file: LocationConvertible, on eventLoop: EventLoop) -> EventLoopFuture { - return get(fileInfo: file, headers: [:], on: eventLoop) + func get(acl file: LocationConvertible) -> EventLoopFuture { + return get(fileInfo: file, headers: [:]) } /// Get file information (HEAD) /// https://docs.aws.amazon.com/AmazonS3/latest/API/RESTObjectHEAD.html - public func get(fileInfo file: LocationConvertible, headers strHeaders: [String: String], on eventLoop: EventLoop) -> EventLoopFuture { + public func get(fileInfo file: LocationConvertible, headers strHeaders: [String: String]) -> EventLoopFuture { let url: URL let headers: HTTPHeaders @@ -32,7 +32,7 @@ extension S3 { return eventLoop.makeFailedFuture(error) } - return make(request: url, method: .HEAD, headers: headers, data: Data(), on: eventLoop).flatMapThrowing { response in + return make(request: url, method: .HEAD, headers: headers, data: Data()).flatMapThrowing { response in try self.check(response) let bucket = file.bucket ?? self.defaultBucket @@ -54,8 +54,8 @@ extension S3 { /// Get file information (HEAD) /// https://docs.aws.amazon.com/AmazonS3/latest/API/RESTObjectHEAD.html - public func get(fileInfo file: LocationConvertible, on eventLoop: EventLoop) -> EventLoopFuture { - return get(fileInfo: file, headers: [:], on: eventLoop) + public func get(fileInfo file: LocationConvertible) -> EventLoopFuture { + return get(fileInfo: file, headers: [:]) } } diff --git a/Sources/S3Kit/Extensions/S3+Put.swift b/Sources/S3Kit/Extensions/S3+Put.swift index 4875e16..6948d19 100755 --- a/Sources/S3Kit/Extensions/S3+Put.swift +++ b/Sources/S3Kit/Extensions/S3+Put.swift @@ -7,7 +7,7 @@ extension S3 { // MARK: Upload /// Upload file to S3 - public func put(file: File.Upload, headers strHeaders: [String: String], on eventLoop: EventLoop) -> EventLoopFuture { + public func put(file: File.Upload, headers strHeaders: [String: String]) -> EventLoopFuture { let headers: HTTPHeaders let url: URL @@ -27,7 +27,7 @@ extension S3 { return eventLoop.makeFailedFuture(error) } - return make(request: url, method: .PUT, headers: headers, data: file.data, on: eventLoop).flatMapThrowing { response in + return make(request: url, method: .PUT, headers: headers, data: file.data).flatMapThrowing { response in try self.check(response) let res = File.Response(data: file.data, bucket: file.bucket ?? self.defaultBucket, path: file.path, access: file.access, mime: file.mime) return res @@ -35,12 +35,12 @@ extension S3 { } /// Upload file to S3 - public func put(file: File.Upload, on eventLoop: EventLoop) -> EventLoopFuture { - return put(file: file, headers: [:], on: eventLoop) + public func put(file: File.Upload) -> EventLoopFuture { + return put(file: file, headers: [:]) } /// Upload file by it's URL to S3 - public func put(file url: URL, destination: String, access: AccessControlList = .privateAccess, on eventLoop: EventLoop) -> EventLoopFuture { + public func put(file url: URL, destination: String, access: AccessControlList = .privateAccess) -> EventLoopFuture { let data: Data do { data = try Data(contentsOf: url) @@ -49,17 +49,17 @@ extension S3 { } let file = File.Upload(data: data, bucket: nil, destination: destination, access: access, mime: mimeType(forFileAtUrl: url)) - return put(file: file, on: eventLoop) + return put(file: file) } /// Upload file by it's path to S3 - public func put(file path: String, destination: String, access: AccessControlList = .privateAccess, on eventLoop: EventLoop) -> EventLoopFuture { + public func put(file path: String, destination: String, access: AccessControlList = .privateAccess) -> EventLoopFuture { let url: URL = URL(fileURLWithPath: path) - return put(file: url, destination: destination, bucket: nil, access: access, on: eventLoop) + return put(file: url, destination: destination, bucket: nil, access: access) } /// Upload file by it's URL to S3, full set - public func put(file url: URL, destination: String, bucket: String?, access: AccessControlList = .privateAccess, on eventLoop: EventLoop) -> EventLoopFuture { + public func put(file url: URL, destination: String, bucket: String?, access: AccessControlList = .privateAccess) -> EventLoopFuture { let data: Data do { data = try Data(contentsOf: url) @@ -68,13 +68,13 @@ extension S3 { } let file = File.Upload(data: data, bucket: bucket, destination: destination, access: access, mime: mimeType(forFileAtUrl: url)) - return put(file: file, on: eventLoop) + return put(file: file) } /// Upload file by it's path to S3, full set - public func put(file path: String, destination: String, bucket: String?, access: AccessControlList = .privateAccess, on eventLoop: EventLoop) -> EventLoopFuture { + public func put(file path: String, destination: String, bucket: String?, access: AccessControlList = .privateAccess) -> EventLoopFuture { let url: URL = URL(fileURLWithPath: path) - return put(file: url, destination: destination, bucket: bucket, access: access, on: eventLoop) + return put(file: url, destination: destination, bucket: bucket, access: access) } } diff --git a/Sources/S3Kit/Extensions/S3+Request.swift b/Sources/S3Kit/Extensions/S3+Request.swift index 4c23d74..34f00f4 100644 --- a/Sources/S3Kit/Extensions/S3+Request.swift +++ b/Sources/S3Kit/Extensions/S3+Request.swift @@ -6,7 +6,7 @@ import AsyncHTTPClient extension S3 { /// Make an S3 request - func make(request url: URL, method: HTTPMethod, headers: HTTPHeaders, data: Data? = nil, cachePolicy: URLRequest.CachePolicy = .useProtocolCachePolicy, on eventLoop: EventLoop) -> EventLoopFuture { + func make(request url: URL, method: HTTPMethod, headers: HTTPHeaders, data: Data? = nil, cachePolicy: URLRequest.CachePolicy = .useProtocolCachePolicy) -> EventLoopFuture { do { let body: HTTPClient.Body? if let data = data { @@ -27,8 +27,8 @@ extension S3 { headers: headers, body: body ) - let client = HTTPClient(eventLoopGroupProvider: .shared(eventLoop)) - return client.execute(request: request) + + return httpClient.execute(request: request, eventLoop: .delegate(on: eventLoop)) } catch { return eventLoop.makeFailedFuture(error) } diff --git a/Sources/S3Kit/Extensions/S3+Service.swift b/Sources/S3Kit/Extensions/S3+Service.swift index e7d72a9..6b74536 100644 --- a/Sources/S3Kit/Extensions/S3+Service.swift +++ b/Sources/S3Kit/Extensions/S3+Service.swift @@ -7,7 +7,7 @@ extension S3 { // MARK: Buckets /// Get list of buckets - public func buckets(on eventLoop: EventLoop) -> EventLoopFuture { + public func buckets() -> EventLoopFuture { let headers: HTTPHeaders let url: URL @@ -18,7 +18,7 @@ extension S3 { return eventLoop.makeFailedFuture(error) } - return make(request: url, method: .GET, headers: headers, data: Data(), on: eventLoop).flatMapThrowing { response in + return make(request: url, method: .GET, headers: headers, data: Data()).flatMapThrowing { response in try self.check(response) return try response.decode(to: BucketsInfo.self) } diff --git a/Sources/S3Kit/Extensions/S3+Strings.swift b/Sources/S3Kit/Extensions/S3+Strings.swift index ea70679..ecba9c5 100755 --- a/Sources/S3Kit/Extensions/S3+Strings.swift +++ b/Sources/S3Kit/Extensions/S3+Strings.swift @@ -5,32 +5,32 @@ import HTTPMediaTypes extension S3 { /// Upload file content to S3, full set - public func put(string: String, mime: HTTPMediaType, destination: String, bucket: String?, access: AccessControlList, on eventLoop: EventLoop) -> EventLoopFuture { + public func put(string: String, mime: HTTPMediaType, destination: String, bucket: String?, access: AccessControlList) -> EventLoopFuture { guard let data: Data = string.data(using: String.Encoding.utf8) else { return eventLoop.makeFailedFuture(Error.badStringData) } let file = File.Upload(data: data, bucket: bucket, destination: destination, access: access, mime: mime.description) - return put(file: file, on: eventLoop) + return put(file: file) } /// Upload file content to S3 - public func put(string: String, mime: HTTPMediaType, destination: String, access: AccessControlList, on eventLoop: EventLoop) -> EventLoopFuture { - return put(string: string, mime: mime, destination: destination, bucket: nil, access: access, on: eventLoop) + public func put(string: String, mime: HTTPMediaType, destination: String, access: AccessControlList) -> EventLoopFuture { + return put(string: string, mime: mime, destination: destination, bucket: nil, access: access) } /// Upload file content to S3 - public func put(string: String, destination: String, access: AccessControlList, on eventLoop: EventLoop) -> EventLoopFuture { - return put(string: string, mime: .plainText, destination: destination, bucket: nil, access: access, on: eventLoop) + public func put(string: String, destination: String, access: AccessControlList) -> EventLoopFuture { + return put(string: string, mime: .plainText, destination: destination, bucket: nil, access: access) } /// Upload file content to S3 - public func put(string: String, mime: HTTPMediaType, destination: String, on eventLoop: EventLoop) -> EventLoopFuture { - return put(string: string, mime: mime, destination: destination, access: .privateAccess, on: eventLoop) + public func put(string: String, mime: HTTPMediaType, destination: String) -> EventLoopFuture { + return put(string: string, mime: mime, destination: destination, access: .privateAccess) } /// Upload file content to S3 - public func put(string: String, destination: String, on eventLoop: EventLoop) -> EventLoopFuture { - return put(string: string, mime: .plainText, destination: destination, bucket: nil, access: .privateAccess, on: eventLoop) + public func put(string: String, destination: String) -> EventLoopFuture { + return put(string: string, mime: .plainText, destination: destination, bucket: nil, access: .privateAccess) } } diff --git a/Sources/S3Kit/Extensions/String+Tools.swift b/Sources/S3Kit/Extensions/String+Tools.swift index 83195b0..fd54f4a 100644 --- a/Sources/S3Kit/Extensions/String+Tools.swift +++ b/Sources/S3Kit/Extensions/String+Tools.swift @@ -11,5 +11,4 @@ extension String { } var bytes: [UInt8] { .init(utf8) } - } diff --git a/Sources/S3Kit/Protocols/S3Client.swift b/Sources/S3Kit/Protocols/S3Client.swift index 4cf4645..9787fd1 100644 --- a/Sources/S3Kit/Protocols/S3Client.swift +++ b/Sources/S3Kit/Protocols/S3Client.swift @@ -4,97 +4,96 @@ import HTTPMediaTypes /// S3 client Protocol -public protocol S3Client { - +public protocol S3Client { /// Get list of objects - func buckets(on eventLoop: EventLoop) -> EventLoopFuture + func buckets() -> EventLoopFuture /// Create a bucket - func create(bucket: String, region: Region?, on eventLoop: EventLoop) -> EventLoopFuture + func create(bucket: String, region: Region?) -> EventLoopFuture /// Delete a bucket wherever it is // func delete(bucket: String, on container: Container) -> EventLoopFuture /// Delete a bucket - func delete(bucket: String, region: Region?, on eventLoop: EventLoop) -> EventLoopFuture + func delete(bucket: String, region: Region?) -> EventLoopFuture /// Get bucket location - func location(bucket: String, on eventLoop: EventLoop) -> EventLoopFuture + func location(bucket: String) -> EventLoopFuture /// Get list of objects - func list(bucket: String, region: Region?, on eventLoop: EventLoop) -> EventLoopFuture + func list(bucket: String, region: Region?) -> EventLoopFuture /// Get list of objects - func list(bucket: String, region: Region?, headers: [String: String], on eventLoop: EventLoop) -> EventLoopFuture + func list(bucket: String, region: Region?, headers: [String: String]) -> EventLoopFuture /// Upload file to S3 - func put(file: File.Upload, on eventLoop: EventLoop) -> EventLoopFuture + func put(file: File.Upload) -> EventLoopFuture /// Upload file to S3 - func put(file: File.Upload, headers: [String: String], on eventLoop: EventLoop) -> EventLoopFuture + func put(file: File.Upload, headers: [String: String]) -> EventLoopFuture /// Upload file to S3 - func put(file url: URL, destination: String, access: AccessControlList, on eventLoop: EventLoop) -> EventLoopFuture + func put(file url: URL, destination: String, access: AccessControlList) -> EventLoopFuture /// Upload file to S3 - func put(file url: URL, destination: String, bucket: String?, access: AccessControlList, on eventLoop: EventLoop) -> EventLoopFuture + func put(file url: URL, destination: String, bucket: String?, access: AccessControlList) -> EventLoopFuture /// Upload file to S3 - func put(file path: String, destination: String, access: AccessControlList, on eventLoop: EventLoop) -> EventLoopFuture + func put(file path: String, destination: String, access: AccessControlList) -> EventLoopFuture /// Upload file to S3 - func put(file path: String, destination: String, bucket: String?, access: AccessControlList, on eventLoop: EventLoop) -> EventLoopFuture + func put(file path: String, destination: String, bucket: String?, access: AccessControlList) -> EventLoopFuture /// Upload file to S3 - func put(string: String, destination: String, on eventLoop: EventLoop) -> EventLoopFuture + func put(string: String, destination: String) -> EventLoopFuture /// Upload file to S3 - func put(string: String, destination: String, access: AccessControlList, on eventLoop: EventLoop) -> EventLoopFuture + func put(string: String, destination: String, access: AccessControlList) -> EventLoopFuture /// Upload file to S3 - func put(string: String, mime: HTTPMediaType, destination: String, on eventLoop: EventLoop) -> EventLoopFuture + func put(string: String, mime: HTTPMediaType, destination: String) -> EventLoopFuture /// Upload file to S3 - func put(string: String, mime: HTTPMediaType, destination: String, access: AccessControlList, on eventLoop: EventLoop) -> EventLoopFuture + func put(string: String, mime: HTTPMediaType, destination: String, access: AccessControlList) -> EventLoopFuture /// Upload file to S3 - func put(string: String, mime: HTTPMediaType, destination: String, bucket: String?, access: AccessControlList, on eventLoop: EventLoop) -> EventLoopFuture + func put(string: String, mime: HTTPMediaType, destination: String, bucket: String?, access: AccessControlList) -> EventLoopFuture /// File URL func url(fileInfo file: LocationConvertible) throws -> URL /// Retrieve file data from S3 - func get(fileInfo file: LocationConvertible, on eventLoop: EventLoop) -> EventLoopFuture + func get(fileInfo file: LocationConvertible) -> EventLoopFuture /// Retrieve file data from S3 - func get(fileInfo file: LocationConvertible, headers: [String: String], on eventLoop: EventLoop) -> EventLoopFuture + func get(fileInfo file: LocationConvertible, headers: [String: String]) -> EventLoopFuture /// Retrieve file data from S3 - func get(file: LocationConvertible, on eventLoop: EventLoop) -> EventLoopFuture + func get(file: LocationConvertible) -> EventLoopFuture /// Retrieve file data from S3 - func get(file: LocationConvertible, headers: [String: String], on eventLoop: EventLoop) -> EventLoopFuture + func get(file: LocationConvertible, headers: [String: String]) -> EventLoopFuture /// Delete file from S3 - func delete(file: LocationConvertible, on eventLoop: EventLoop) -> EventLoopFuture + func delete(file: LocationConvertible) -> EventLoopFuture /// Delete file from S3 - func delete(file: LocationConvertible, headers: [String: String], on eventLoop: EventLoop) -> EventLoopFuture + func delete(file: LocationConvertible, headers: [String: String]) -> EventLoopFuture /// Copy file on S3 - func copy(file: LocationConvertible, to: LocationConvertible, headers: [String: String], on eventLoop: EventLoop) -> EventLoopFuture + func copy(file: LocationConvertible, to: LocationConvertible, headers: [String: String]) -> EventLoopFuture } extension S3Client { /// Retrieve file data from S3 - func get(file: LocationConvertible, on eventLoop: EventLoop) -> EventLoopFuture { - return get(file: file, headers: [:], on: eventLoop) + func get(file: LocationConvertible) -> EventLoopFuture { + return get(file: file, headers: [:]) } /// Copy file on S3 - public func copy(file: LocationConvertible, to: LocationConvertible, on eventLoop: EventLoop) -> EventLoopFuture { - return self.copy(file: file, to: to, headers: [:], on: eventLoop) + public func copy(file: LocationConvertible, to: LocationConvertible) -> EventLoopFuture { + return self.copy(file: file, to: to, headers: [:]) } static var dateFormatter: DateFormatter { diff --git a/Sources/S3Kit/S3.swift b/Sources/S3Kit/S3.swift index a485f73..afbe8ae 100755 --- a/Sources/S3Kit/S3.swift +++ b/Sources/S3Kit/S3.swift @@ -6,6 +6,8 @@ import AsyncHTTPClient /// Main S3 class public class S3: S3Client { + public let eventLoop: EventLoop + public let httpClient: HTTPClient /// Error messages public enum Error: Swift.Error { @@ -30,23 +32,27 @@ public class S3: S3Client { // MARK: Initialization /// Basic initialization method, also registers S3Signer and self with services - @discardableResult public convenience init(defaultBucket: String, config: S3Signer.Config) throws { - let signer = try S3Signer(config) - try self.init(defaultBucket: defaultBucket, signer: signer) + public convenience init(config: S3Signer.Config, eventLoop: EventLoop, httpClient: HTTPClient) { + let signer = S3Signer(config) + self.init(defaultBucket: config.defaultBucket, signer: signer, eventLoop: eventLoop, httpClient: httpClient) } /// Basic initialization method - public init(defaultBucket: String, signer: S3Signer) throws { + public init(defaultBucket: String, signer: S3Signer, eventLoop: EventLoop, httpClient: HTTPClient) { self.defaultBucket = defaultBucket self.signer = signer self.urlBuilder = nil + self.eventLoop = eventLoop + self.httpClient = httpClient } /// Basic initialization method - public init(urlBuilder: URLBuilder, defaultBucket: String, signer: S3Signer) throws { + public init(urlBuilder: URLBuilder, defaultBucket: String, signer: S3Signer, eventLoop: EventLoop, httpClient: HTTPClient) { self.defaultBucket = defaultBucket self.signer = signer self.urlBuilder = nil + self.eventLoop = eventLoop + self.httpClient = httpClient } } diff --git a/Sources/S3Signer/Extensions/Data+Hex.swift b/Sources/S3Signer/Extensions/Data+Hex.swift new file mode 100644 index 0000000..433ddd9 --- /dev/null +++ b/Sources/S3Signer/Extensions/Data+Hex.swift @@ -0,0 +1,5 @@ +extension Data { + var hexString: String { + return self.reduce("", { $0 + String(format: "%02x", $1) }) + } +} diff --git a/Sources/S3Signer/Extensions/HMAC+Tools.swift b/Sources/S3Signer/Extensions/HMAC+Tools.swift index a4c2238..1438842 100644 --- a/Sources/S3Signer/Extensions/HMAC+Tools.swift +++ b/Sources/S3Signer/Extensions/HMAC+Tools.swift @@ -1,5 +1,4 @@ -import OpenCrypto - +import Crypto extension HMAC { @@ -26,5 +25,4 @@ extension HashedAuthenticationCode { var data: Data { return Data(self) } - } diff --git a/Sources/S3Signer/Extensions/S3Signer+Private.swift b/Sources/S3Signer/Extensions/S3Signer+Private.swift index ee00b5b..b7d5d2d 100755 --- a/Sources/S3Signer/Extensions/S3Signer+Private.swift +++ b/Sources/S3Signer/Extensions/S3Signer+Private.swift @@ -1,5 +1,5 @@ import Foundation -import OpenCrypto +import Crypto import NIOHTTP1 import HTTPMediaTypes @@ -55,11 +55,11 @@ extension S3Signer { let dateRegionServiceKey = HMAC.signature(config.service, key: dateRegionKey) let signingKey = HMAC.signature("aws4_request", key: dateRegionServiceKey) let signature = HMAC.signature(stringToSign, key: signingKey) - return signature.description + return Data(signature).hexString } func createStringToSign(_ canonicalRequest: String, dates: Dates, region: Region) throws -> String { - let canonRequestHash = SHA256.hash(data: canonicalRequest.bytes).description + let canonRequestHash = Data(SHA256.hash(data: canonicalRequest.bytes)).hexString let components = [ "AWS4-HMAC-SHA256", dates.long, diff --git a/Sources/S3Signer/Payload.swift b/Sources/S3Signer/Payload.swift index 025e8a2..cca390c 100755 --- a/Sources/S3Signer/Payload.swift +++ b/Sources/S3Signer/Payload.swift @@ -1,5 +1,5 @@ import Foundation -import OpenCrypto +import Crypto /// Payload object @@ -30,9 +30,9 @@ extension Payload { func hashed() -> String { switch self { case .bytes(let bytes): - return SHA256.hash(data: [UInt8](bytes)).description + return Data(SHA256.hash(data: [UInt8](bytes))).hexString case .none: - return SHA256.hash(data: []).description + return Data(SHA256.hash(data: [])).hexString case .unsigned: return "UNSIGNED-PAYLOAD" } diff --git a/Sources/S3Signer/Region.swift b/Sources/S3Signer/Region.swift index 596235b..a45e6b8 100755 --- a/Sources/S3Signer/Region.swift +++ b/Sources/S3Signer/Region.swift @@ -32,6 +32,9 @@ public struct Region { /// EU (Frankfurt) public static let euCentral1: Name = "eu-central-1" + /// EU (Stockholm) + public static let euNorth1: Name = "eu-north-1" + /// EU (Ireland) public static let euWest1: Name = "eu-west-1" @@ -124,6 +127,9 @@ extension Region { /// convenience var for EU (Frankfurt) public static let euCentral1 = Region(name: .euCentral1) + /// EU (Stockholm) + public static let euNorth1 = Region(name: .euNorth1) + /// convenience var for EU (Ireland) public static let euWest1 = Region(name: .euWest1) diff --git a/Sources/S3Signer/S3Signer.swift b/Sources/S3Signer/S3Signer.swift index e17e7fd..fe8ff98 100755 --- a/Sources/S3Signer/S3Signer.swift +++ b/Sources/S3Signer/S3Signer.swift @@ -1,5 +1,5 @@ import Foundation -import OpenCrypto +import Crypto import NIOHTTP1 import WebErrorKit @@ -65,6 +65,9 @@ public final class S3Signer { /// AWS Service type let service: String = "s3" + /// Default bucket name + public let defaultBucket: String + /// Initalizer /// - Parameter accessKey: S3 access token @@ -72,12 +75,13 @@ public final class S3Signer { /// - Parameter region: Region /// - Parameter version: Signing version /// - Parameter securityToken: Temporary security token - public init(accessKey: String, secretKey: String, region: Region, version: Version = .v4, securityToken: String? = nil) { + public init(accessKey: String, secretKey: String, region: Region, version: Version = .v4, securityToken: String? = nil, defaultBucket: String) { self.accessKey = accessKey self.secretKey = secretKey self.region = region self.securityToken = securityToken self.authVersion = version + self.defaultBucket = defaultBucket } } @@ -86,7 +90,7 @@ public final class S3Signer { public private(set) var config: Config /// Initializer - public init(_ config: Config) throws { + public init(_ config: Config) { self.config = config } diff --git a/Tests/S3Tests/BaseTestCase.swift b/Tests/S3Tests/BaseTestCase.swift index bfb7b10..cbc1d0b 100644 --- a/Tests/S3Tests/BaseTestCase.swift +++ b/Tests/S3Tests/BaseTestCase.swift @@ -14,12 +14,12 @@ class BaseTestCase: XCTestCase { override func setUp() { super.setUp() region = Region.usEast1 - signer = try! S3Signer(S3Signer.Config(accessKey: accessKey, secretKey: secretKey, region: region)) + signer = try! S3Signer(S3Signer.Config(accessKey: accessKey, secretKey: secretKey, region: region, defaultBucket: "")) // this is the "seconds" representation of "20130524T000000Z" overridenDate = Dates(Date(timeIntervalSince1970: (60*60*24) * 15849)) - if let s = try? S3Signer(S3Signer.Config(accessKey: accessKey, secretKey: secretKey, region: region)) { + if let s = try? S3Signer(S3Signer.Config(accessKey: accessKey, secretKey: secretKey, region: region, defaultBucket: "")) { signer = s } else { XCTFail("Could not intialize signer") diff --git a/Tests/S3Tests/S3SignerV2Tests.swift b/Tests/S3Tests/S3SignerV2Tests.swift index 96f5fd0..e1d6c67 100644 --- a/Tests/S3Tests/S3SignerV2Tests.swift +++ b/Tests/S3Tests/S3SignerV2Tests.swift @@ -5,12 +5,7 @@ import XCTest class S3SignerV2Tests: BaseTestCase { override func setUp() { super.setUp() - signer = try! S3Signer(S3Signer.Config(accessKey: accessKey, secretKey: secretKey, region: region, version: .v2)) - if let s = try? S3Signer(S3Signer.Config(accessKey: accessKey, secretKey: secretKey, region: region, version: .v2)) { - signer = s - } else { - XCTFail("Could not intialize signer") - } + signer = S3Signer(S3Signer.Config(accessKey: accessKey, secretKey: secretKey, region: region, version: .v2, defaultBucket: "")) } // TOOD: appropriate testing would try various cases (where URL has signable query items, etc)