Skip to content

Commit 4fe4eeb

Browse files
committed
loads of changes
- locating bucket region works - new url builder - loads of cleaning & small improvements
1 parent 5364051 commit 4fe4eeb

19 files changed

+375
-145
lines changed
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
{
2+
"info": {
3+
"_postman_id": "da87b927-b230-431d-91bf-57200936e59f",
4+
"name": "Vapor S3",
5+
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
6+
},
7+
"item": [
8+
{
9+
"name": "Buckets list",
10+
"request": {
11+
"method": "GET",
12+
"header": [],
13+
"body": {},
14+
"url": {
15+
"raw": "{{SERVER}}buckets",
16+
"host": [
17+
"{{SERVER}}buckets"
18+
]
19+
}
20+
},
21+
"response": []
22+
},
23+
{
24+
"name": "Bucket (create)",
25+
"request": {
26+
"method": "PUT",
27+
"header": [],
28+
"body": {},
29+
"url": {
30+
"raw": "{{SERVER}}bucket",
31+
"host": [
32+
"{{SERVER}}bucket"
33+
]
34+
}
35+
},
36+
"response": []
37+
},
38+
{
39+
"name": "Bucket (delete)",
40+
"request": {
41+
"method": "DELETE",
42+
"header": [],
43+
"body": {},
44+
"url": {
45+
"raw": "{{SERVER}}bucket",
46+
"host": [
47+
"{{SERVER}}bucket"
48+
]
49+
}
50+
},
51+
"response": []
52+
},
53+
{
54+
"name": "Bucket (location)",
55+
"request": {
56+
"method": "GET",
57+
"header": [],
58+
"body": {},
59+
"url": {
60+
"raw": "{{SERVER}}bucket/location",
61+
"host": [
62+
"{{SERVER}}bucket"
63+
],
64+
"path": [
65+
"location"
66+
]
67+
}
68+
},
69+
"response": []
70+
},
71+
{
72+
"name": "Files (list)",
73+
"request": {
74+
"method": "GET",
75+
"header": [],
76+
"body": {},
77+
"url": {
78+
"raw": "{{SERVER}}files",
79+
"host": [
80+
"{{SERVER}}files"
81+
]
82+
}
83+
},
84+
"response": []
85+
},
86+
{
87+
"name": "Files (test)",
88+
"request": {
89+
"method": "GET",
90+
"header": [],
91+
"body": {},
92+
"url": {
93+
"raw": "{{SERVER}}files/test",
94+
"host": [
95+
"{{SERVER}}files"
96+
],
97+
"path": [
98+
"test"
99+
]
100+
}
101+
},
102+
"response": []
103+
}
104+
]
105+
}

Package.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ let package = Package(
99
.library(name: "S3TestTools", targets: ["S3TestTools"])
1010
],
1111
dependencies: [
12-
.package(url: "https://github.com/vapor/vapor.git", from: "3.0.0-rc.2"),
13-
.package(url: "https://github.com/LiveUI/XMLCoding.git", .branch("master")),
12+
.package(url: "https://github.com/vapor/vapor.git", from: "3.0.0"),
13+
.package(url: "https://github.com/LiveUI/XMLCoding.git", from: "0.1.0"),
1414
.package(url: "https://github.com/LiveUI/VaporTestTools.git", .branch("master"))
1515
],
1616
targets: [

README.md

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
- [x] Listing buckets
77
- [x] Create bucket
88
- [x] Delete bucket
9+
- [x] Locate bucket region
910
- [x] List objects
1011
- [x] Upload file
1112
- [x] Get file
@@ -54,14 +55,11 @@ public protocol S3Client: Service {
5455
/// Create a bucket
5556
func create(bucket: String, region: Region?, on container: Container) throws -> Future<Void>
5657

57-
/// Delete a bucket wherever it is (WIP)
58-
// func delete(bucket: String, on container: Container) throws -> Future<Void>
59-
6058
/// Delete a bucket
6159
func delete(bucket: String, region: Region?, on container: Container) throws -> Future<Void>
6260

63-
/// Get bucket location (WIP)
64-
// func location(bucket: String, on container: Container) throws -> Future<Bucket.Location>
61+
/// Get bucket location
62+
func location(bucket: String, on container: Container) throws -> Future<Region>
6563

6664
/// Get list of objects
6765
func list(bucket: String, region: Region?, on container: Container) throws -> Future<BucketResults>
@@ -144,6 +142,24 @@ public func routes(_ router: Router) throws {
144142
)
145143
}
146144

145+
// Locate bucket (get region)
146+
router.get("bucket/location") { req -> Future<String> in
147+
let s3 = try req.makeS3Client()
148+
return try s3.location(bucket: "bucket-name", on: req).map(to: String.self) { region in
149+
return region.hostUrlString()
150+
}.catchMap({ (error) -> (String) in
151+
if let error = error as? S3.Error {
152+
switch error {
153+
case .errorResponse(_, let error):
154+
return error.message
155+
default:
156+
return "S3 :("
157+
}
158+
}
159+
return ":("
160+
}
161+
)
162+
}
147163
// Delete bucket
148164
router.delete("bucket") { req -> Future<String> in
149165
let s3 = try req.makeS3Client()

Sources/S3/Extensions/S3+Bucket.swift

Lines changed: 45 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -15,44 +15,51 @@ public extension S3 {
1515

1616
// MARK: Buckets
1717

18-
// /// Get bucket location
19-
// public func location(bucket: String, on container: Container) throws -> Future<Bucket.Location> {
20-
// let region = region ?? signer.config.region
21-
// guard let url = URL(string: "https://\(bucket).s3.amazonaws.com/?location"), let host = url.host else {
22-
// throw Error.invalidUrl
23-
// }
24-
//
25-
// let headers = [
26-
// "host": host
27-
// ]
28-
// let awsHeaders = try signer.headers(for: .GET, urlString: url.absoluteString, region: region, headers: headers, payload: .none)
29-
// return try make(request: url, method: .GET, headers: awsHeaders, data: "".convertToData(), on: container).map(to: Bucket.Location.self) { response in
30-
// if response.http.status == .notFound {
31-
// throw Error.notFound
32-
// }
33-
// guard response.http.status == .ok || response.http.status == .noContent else {
34-
// if let error = try? response.decode(to: ErrorMessage.self) {
35-
// throw Error.errorResponse(response.http.status, error)
36-
// } else {
37-
// throw Error.badResponse(response)
38-
// }
39-
// }
40-
// return try response.decode(to: Bucket.Location.self)
41-
// }
42-
// }
18+
/// Get bucket location
19+
public func location(bucket: String, on container: Container) throws -> Future<Region> {
20+
let builder = urlBuilder(for: container)
21+
let region = Region.euWest2
22+
let url = try builder.url(region: region, bucket: bucket)
23+
24+
let awsHeaders = try signer.headers(for: .GET, urlString: url.absoluteString, region: region, payload: .none)
25+
return try make(request: url, method: .GET, headers: awsHeaders, data: emptyData(), on: container).map(to: Region.self) { response in
26+
if response.http.status == .notFound {
27+
throw Error.notFound
28+
}
29+
if response.http.status == .ok {
30+
return region
31+
} else {
32+
if let error = try? response.decode(to: ErrorMessage.self), error.code == "PermanentRedirect", let endpoint = error.endpoint {
33+
if endpoint == "s3.amazonaws.com" {
34+
return Region.usEast1
35+
} else {
36+
// Split bucket.s3.region.amazonaws.com into parts
37+
var parts = endpoint.split(separator: ".")
38+
// Remove .com
39+
parts.removeLast()
40+
// Remove .amazonaws
41+
parts.removeLast()
42+
// Get region (lat part)
43+
let regionString = String(parts.removeLast()).lowercased()
44+
guard let region = Region(rawValue: regionString) else {
45+
throw Error.badResponse(response)
46+
}
47+
return region
48+
}
49+
} else {
50+
throw Error.badResponse(response)
51+
}
52+
}
53+
}
54+
}
4355

4456
/// Delete bucket
4557
public func delete(bucket: String, region: Region? = nil, on container: Container) throws -> Future<Void> {
46-
let region = region ?? signer.config.region
47-
guard let url = URL(string: "https://\(bucket).s3.\(region.rawValue).amazonaws.com/"), let host = url.host else {
48-
throw Error.invalidUrl
49-
}
58+
let builder = urlBuilder(for: container)
59+
let url = try builder.url(region: region, bucket: bucket)
5060

51-
let headers = [
52-
"host": host
53-
]
54-
let awsHeaders = try signer.headers(for: .DELETE, urlString: url.absoluteString, region: region, headers: headers, payload: .none)
55-
return try make(request: url, method: .DELETE, headers: awsHeaders, data: "".convertToData(), on: container).map(to: Void.self) { response in
61+
let awsHeaders = try signer.headers(for: .DELETE, urlString: url.absoluteString, region: region, payload: .none)
62+
return try make(request: url, method: .DELETE, headers: awsHeaders, data: emptyData(), on: container).map(to: Void.self) { response in
5663
try self.check(response)
5764
return Void()
5865
}
@@ -61,9 +68,9 @@ public extension S3 {
6168
/// Create a bucket
6269
public func create(bucket: String, region: Region? = nil, on container: Container) throws -> Future<Void> {
6370
let region = region ?? signer.config.region
64-
guard let url = URL(string: "https://\(bucket).s3.\(region.rawValue).amazonaws.com/"), let host = url.host else {
65-
throw Error.invalidUrl
66-
}
71+
72+
let builder = urlBuilder(for: container)
73+
let url = try builder.url(region: region, bucket: bucket)
6774

6875
let content = """
6976
<CreateBucketConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
@@ -72,11 +79,7 @@ public extension S3 {
7279
"""
7380

7481
let data = content.convertToData()
75-
76-
let headers = [
77-
"host": host
78-
]
79-
let awsHeaders = try signer.headers(for: .PUT, urlString: url.absoluteString, region: region, headers: headers, payload: .bytes(data))
82+
let awsHeaders = try signer.headers(for: .PUT, urlString: url.absoluteString, region: region, payload: .bytes(data))
8083
return try make(request: url, method: .PUT, headers: awsHeaders, data: data, on: container).map(to: Void.self) { response in
8184
try self.check(response)
8285
return Void()

Sources/S3/Extensions/S3+Delete.swift

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,11 @@ public extension S3 {
1616

1717
/// Delete file from S3
1818
public func delete(file: LocationConvertible, headers: [String: String], on container: Container) throws -> Future<Void> {
19-
let url = try self.url(file: file, on: container)
19+
let builder = urlBuilder(for: container)
20+
let url = try builder.url(file: file)
21+
2022
let headers = try signer.headers(for: .DELETE, urlString: url.absoluteString, headers: headers, payload: .none)
21-
return try make(request: url, method: .DELETE, headers: headers, data: "".convertToData(), on: container).map(to: Void.self) { response in
23+
return try make(request: url, method: .DELETE, headers: headers, data: emptyData(), on: container).map(to: Void.self) { response in
2224
try self.check(response)
2325

2426
return Void()

Sources/S3/Extensions/S3+Get.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@ public extension S3 {
1616

1717
/// Retrieve file data from S3
1818
public func get(file: LocationConvertible, headers: [String: String], on container: Container) throws -> Future<File.Response> {
19-
let url = try self.url(file: file, on: container)
19+
let builder = urlBuilder(for: container)
20+
let url = try builder.url(file: file)
21+
2022
let headers = try signer.headers(for: .GET, urlString: url.absoluteString, headers: headers, payload: .none)
2123
return try make(request: url, method: .GET, headers: headers, on: container).map(to: File.Response.self) { response in
2224
try self.check(response)

Sources/S3/Extensions/S3+List.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ extension S3 {
2727
var headers = headers
2828
headers["host"] = host
2929
let awsHeaders = try signer.headers(for: .GET, urlString: url.absoluteString, region: region, headers: headers, payload: .none)
30-
return try make(request: url, method: .GET, headers: awsHeaders, data: "".convertToData(), on: container).map(to: BucketResults.self) { response in
30+
return try make(request: url, method: .GET, headers: awsHeaders, data: emptyData(), on: container).map(to: BucketResults.self) { response in
3131
try self.check(response)
3232
return try response.decode(to: BucketResults.self)
3333
}

Sources/S3/Extensions/S3+ObjectInfo.swift

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,11 @@ public extension S3 {
3030
/// Get file information (HEAD)
3131
/// https://docs.aws.amazon.com/AmazonS3/latest/API/RESTObjectHEAD.html
3232
public func get(fileInfo file: LocationConvertible, headers: [String: String], on container: Container) throws -> Future<File.Info> {
33-
let url = try self.url(file: file, on: container)
33+
let builder = urlBuilder(for: container)
34+
let url = try builder.url(file: file)
35+
3436
let headers = try signer.headers(for: .HEAD, urlString: url.absoluteString, headers: headers, payload: .none)
35-
return try make(request: url, method: .HEAD, headers: headers, data: "".convertToData(), on: container).map(to: File.Info.self) { response in
37+
return try make(request: url, method: .HEAD, headers: headers, data: emptyData(), on: container).map(to: File.Info.self) { response in
3638
try self.check(response)
3739

3840
let bucket = file.bucket ?? self.defaultBucket

Sources/S3/Extensions/S3+Put.swift

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,10 @@ public extension S3 {
1717

1818
/// Upload file to S3
1919
public func put(file: File.Upload, headers: [String: String], on container: Container) throws -> EventLoopFuture<File.Response> {
20-
let url = try self.url(file: file, on: container)
20+
let builder = urlBuilder(for: container)
21+
let url = try builder.url(file: file)
22+
23+
// let url = URL(string: "https://s3.eu-west-2.amazonaws.com/s3-liveui-test/file-hu.txt")!
2124

2225
var awsHeaders: [String: String] = headers
2326
awsHeaders["content-type"] = file.mime.description

Sources/S3/Extensions/S3+Service.swift

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,10 @@ public extension S3 {
1717

1818
/// Get list of buckets
1919
public func buckets(on container: Container) throws -> Future<BucketsInfo> {
20-
let url = try self.url(on: container)
20+
let builder = urlBuilder(for: container)
21+
let url = try builder.plain()
2122
let headers = try signer.headers(for: .GET, urlString: url.absoluteString, payload: .none)
22-
return try make(request: url, method: .GET, headers: headers, data: "".convertToData(), on: container).map(to: BucketsInfo.self) { response in
23+
return try make(request: url, method: .GET, headers: headers, data: emptyData(), on: container).map(to: BucketsInfo.self) { response in
2324
try self.check(response)
2425
return try response.decode(to: BucketsInfo.self)
2526
}

0 commit comments

Comments
 (0)