add s3 exception for virtual host addressing
This commit is contained in:
		
							parent
							
								
									c531164cfa
								
							
						
					
					
						commit
						b9aaffb08d
					
				
					 1 changed files with 123 additions and 22 deletions
				
			
		
							
								
								
									
										145
									
								
								src/aws_http.zig
									
										
									
									
									
								
							
							
						
						
									
										145
									
								
								src/aws_http.zig
									
										
									
									
									
								
							|  | @ -56,11 +56,13 @@ const EndPoint = struct { | |||
|     host: []const u8, | ||||
|     scheme: []const u8, | ||||
|     port: u16, | ||||
|     path: []const u8, | ||||
|     allocator: std.mem.Allocator, | ||||
| 
 | ||||
|     fn deinit(self: EndPoint) void { | ||||
|         self.allocator.free(self.uri); | ||||
|         self.allocator.free(self.host); | ||||
|         self.allocator.free(self.path); | ||||
|     } | ||||
| }; | ||||
| pub const AwsHttp = struct { | ||||
|  | @ -112,7 +114,7 @@ pub const AwsHttp = struct { | |||
|         // S3 control uses <account-id>.s3-control.<region>.amazonaws.com | ||||
|         // | ||||
|         // So this regionSubDomain call needs to handle generic customization | ||||
|         const endpoint = try endpointForRequest(self.allocator, service, options.region, options.dualstack); | ||||
|         const endpoint = try endpointForRequest(self.allocator, service, request, options); | ||||
|         defer endpoint.deinit(); | ||||
|         log.debug("Calling endpoint {s}", .{endpoint.uri}); | ||||
|         // TODO: Should we allow customization here? | ||||
|  | @ -145,12 +147,17 @@ pub const AwsHttp = struct { | |||
|     pub fn makeRequest(self: Self, endpoint: EndPoint, request: HttpRequest, signing_config: ?signing.Config) !HttpResult { | ||||
|         var request_cp = request; | ||||
| 
 | ||||
|         log.debug("Path: {s}", .{request_cp.path}); | ||||
|         log.debug("Request Path: {s}", .{request_cp.path}); | ||||
|         log.debug("Endpoint Path (actually used): {s}", .{endpoint.path}); | ||||
|         log.debug("Query: {s}", .{request_cp.query}); | ||||
|         log.debug("Method: {s}", .{request_cp.method}); | ||||
|         log.debug("body length: {d}", .{request_cp.body.len}); | ||||
|         log.debug("Body\n====\n{s}\n====", .{request_cp.body}); | ||||
| 
 | ||||
|         // Endpoint calculation might be different from the request (e.g. S3 requests) | ||||
|         // We will use endpoint instead | ||||
|         request_cp.path = endpoint.path; | ||||
| 
 | ||||
|         var request_headers = std.ArrayList(base.Header).init(self.allocator); | ||||
|         defer request_headers.deinit(); | ||||
| 
 | ||||
|  | @ -176,7 +183,7 @@ pub const AwsHttp = struct { | |||
|             log.debug("\t{s}: {s}", .{ h.name, h.value }); | ||||
|         } | ||||
| 
 | ||||
|         const url = try std.fmt.allocPrint(self.allocator, "{s}{s}{s}", .{ endpoint.uri, request.path, request.query }); | ||||
|         const url = try std.fmt.allocPrint(self.allocator, "{s}{s}{s}", .{ endpoint.uri, request_cp.path, request_cp.query }); | ||||
|         defer self.allocator.free(url); | ||||
|         log.debug("Request url: {s}", .{url}); | ||||
|         // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! | ||||
|  | @ -253,32 +260,26 @@ fn getEnvironmentVariable(allocator: std.mem.Allocator, key: []const u8) !?[]con | |||
|     }; | ||||
| } | ||||
| 
 | ||||
| fn endpointForRequest(allocator: std.mem.Allocator, service: []const u8, region: []const u8, use_dual_stack: bool) !EndPoint { | ||||
| fn endpointForRequest(allocator: std.mem.Allocator, service: []const u8, request: HttpRequest, options: Options) !EndPoint { | ||||
|     const environment_override = try getEnvironmentVariable(allocator, "AWS_ENDPOINT_URL"); | ||||
|     if (environment_override) |override| { | ||||
|         const uri = try allocator.dupeZ(u8, override); | ||||
|         return endPointFromUri(allocator, uri); | ||||
|     } | ||||
|     if (std.mem.eql(u8, service, "cloudfront")) { | ||||
|         return EndPoint{ | ||||
|             .uri = try allocator.dupe(u8, "https://cloudfront.amazonaws.com"), | ||||
|             .host = try allocator.dupe(u8, "cloudfront.amazonaws.com"), | ||||
|             .scheme = "https", | ||||
|             .port = 443, | ||||
|             .allocator = allocator, | ||||
|         }; | ||||
|     } | ||||
|     // Fallback to us-east-1 if global endpoint does not exist. | ||||
|     const realregion = if (std.mem.eql(u8, region, "aws-global")) "us-east-1" else region; | ||||
|     const dualstack = if (use_dual_stack) ".dualstack" else ""; | ||||
|     const realregion = if (std.mem.eql(u8, options.region, "aws-global")) "us-east-1" else options.region; | ||||
|     const dualstack = if (options.dualstack) ".dualstack" else ""; | ||||
| 
 | ||||
|     const domain = switch (std.hash_map.hashString(region)) { | ||||
|     const domain = switch (std.hash_map.hashString(options.region)) { | ||||
|         US_ISO_EAST_1_HASH => "c2s.ic.gov", | ||||
|         CN_NORTH_1_HASH, CN_NORTHWEST_1_HASH => "amazonaws.com.cn", | ||||
|         US_ISOB_EAST_1_HASH => "sc2s.sgov.gov", | ||||
|         else => "amazonaws.com", | ||||
|     }; | ||||
| 
 | ||||
|     if (try endpointException(allocator, service, request, options, realregion, dualstack, domain)) |e| | ||||
|         return e; | ||||
| 
 | ||||
|     const uri = try std.fmt.allocPrintZ(allocator, "https://{s}{s}.{s}.{s}", .{ service, dualstack, realregion, domain }); | ||||
|     const host = try allocator.dupe(u8, uri["https://".len..]); | ||||
|     log.debug("host: {s}, scheme: {s}, port: {}", .{ host, "https", 443 }); | ||||
|  | @ -288,9 +289,68 @@ fn endpointForRequest(allocator: std.mem.Allocator, service: []const u8, region: | |||
|         .scheme = "https", | ||||
|         .port = 443, | ||||
|         .allocator = allocator, | ||||
|         .path = try allocator.dupe(u8, request.path), | ||||
|     }; | ||||
| } | ||||
| 
 | ||||
| fn endpointException( | ||||
|     allocator: std.mem.Allocator, | ||||
|     service: []const u8, | ||||
|     request: HttpRequest, | ||||
|     options: Options, | ||||
|     realregion: []const u8, | ||||
|     dualstack: []const u8, | ||||
|     domain: []const u8, | ||||
| ) !?EndPoint { | ||||
|     if (std.mem.eql(u8, service, "cloudfront")) { | ||||
|         return EndPoint{ | ||||
|             .uri = try allocator.dupe(u8, "https://cloudfront.amazonaws.com"), | ||||
|             .host = try allocator.dupe(u8, "cloudfront.amazonaws.com"), | ||||
|             .scheme = "https", | ||||
|             .port = 443, | ||||
|             .allocator = allocator, | ||||
|             .path = try allocator.dupe(u8, request.path), | ||||
|         }; | ||||
|     } | ||||
|     if (std.mem.eql(u8, service, "s3")) { | ||||
|         if (request.path.len == 1 or std.mem.indexOf(u8, request.path[1..], "/") == null) | ||||
|             return null; | ||||
| 
 | ||||
|         // We need to adjust the host and the path to accomodate virtual | ||||
|         // host addressing. This only applies to bucket operations, but | ||||
|         // right now I'm hoping that bucket operations do not include a path | ||||
|         // component, so will be handled by the return null statement above. | ||||
|         const bucket_name = s3BucketFromPath(request.path); | ||||
|         const rest_of_path = request.path[bucket_name.len + 1 ..]; | ||||
|         // TODO: Implement | ||||
|         _ = options; | ||||
|         const uri = try std.fmt.allocPrintZ(allocator, "https://{s}.{s}{s}.{s}.{s}", .{ bucket_name, service, dualstack, realregion, domain }); | ||||
|         const host = try allocator.dupe(u8, uri["https://".len..]); | ||||
|         log.debug("S3 host: {s}, scheme: {s}, port: {}", .{ host, "https", 443 }); | ||||
|         return EndPoint{ | ||||
|             .uri = uri, | ||||
|             .host = host, | ||||
|             .scheme = "https", | ||||
|             .port = 443, | ||||
|             .allocator = allocator, | ||||
|             .path = try allocator.dupe(u8, rest_of_path), | ||||
|         }; | ||||
|     } | ||||
|     return null; | ||||
| } | ||||
| 
 | ||||
| fn s3BucketFromPath(path: []const u8) []const u8 { | ||||
|     var in_bucket = false; | ||||
|     var start: usize = 0; | ||||
|     for (path) |c, inx| { | ||||
|         if (c == '/') { | ||||
|             if (in_bucket) return path[start..inx]; | ||||
|             start = inx + 1; | ||||
|             in_bucket = true; | ||||
|         } | ||||
|     } | ||||
|     unreachable; | ||||
| } | ||||
| /// creates an endpoint from a uri string. | ||||
| /// | ||||
| /// allocator: Will be used only to construct the EndPoint struct | ||||
|  | @ -337,27 +397,68 @@ fn endPointFromUri(allocator: std.mem.Allocator, uri: []const u8) !EndPoint { | |||
|         .scheme = scheme, | ||||
|         .allocator = allocator, | ||||
|         .port = port, | ||||
|         .path = try allocator.dupe(u8, "/"), | ||||
|     }; | ||||
| } | ||||
| 
 | ||||
| test "endpointForRequest standard operation" { | ||||
|     const request: HttpRequest = .{}; | ||||
|     const options: Options = .{ | ||||
|         .region = "us-west-2", | ||||
|         .dualstack = false, | ||||
|         .sigv4_service_name = null, | ||||
|     }; | ||||
|     const allocator = std.testing.allocator; | ||||
|     const service = "dynamodb"; | ||||
|     const region = "us-west-2"; | ||||
|     const use_dual_stack = false; | ||||
| 
 | ||||
|     const endpoint = try endpointForRequest(allocator, service, region, use_dual_stack); | ||||
|     const endpoint = try endpointForRequest(allocator, service, request, options); | ||||
|     defer endpoint.deinit(); | ||||
|     try std.testing.expectEqualStrings("https://dynamodb.us-west-2.amazonaws.com", endpoint.uri); | ||||
| } | ||||
| 
 | ||||
| test "endpointForRequest for cloudfront" { | ||||
|     const request = HttpRequest{}; | ||||
|     const options = Options{ | ||||
|         .region = "us-west-2", | ||||
|         .dualstack = false, | ||||
|         .sigv4_service_name = null, | ||||
|     }; | ||||
|     const allocator = std.testing.allocator; | ||||
|     const service = "cloudfront"; | ||||
|     const region = "us-west-2"; | ||||
|     const use_dual_stack = false; | ||||
| 
 | ||||
|     const endpoint = try endpointForRequest(allocator, service, region, use_dual_stack); | ||||
|     const endpoint = try endpointForRequest(allocator, service, request, options); | ||||
|     defer endpoint.deinit(); | ||||
|     try std.testing.expectEqualStrings("https://cloudfront.amazonaws.com", endpoint.uri); | ||||
| } | ||||
| 
 | ||||
| test "endpointForRequest for s3" { | ||||
|     const request = HttpRequest{}; | ||||
|     const options = Options{ | ||||
|         .region = "us-east-2", | ||||
|         .dualstack = false, | ||||
|         .sigv4_service_name = null, | ||||
|     }; | ||||
|     const allocator = std.testing.allocator; | ||||
|     const service = "s3"; | ||||
| 
 | ||||
|     const endpoint = try endpointForRequest(allocator, service, request, options); | ||||
|     defer endpoint.deinit(); | ||||
|     try std.testing.expectEqualStrings("https://s3.us-east-2.amazonaws.com", endpoint.uri); | ||||
| } | ||||
| test "endpointForRequest for s3 - specific bucket" { | ||||
|     const request = HttpRequest{ | ||||
|         .path = "/bucket/key", | ||||
|     }; | ||||
|     const options = Options{ | ||||
|         .region = "us-east-2", | ||||
|         .dualstack = false, | ||||
|         .sigv4_service_name = null, | ||||
|     }; | ||||
|     const allocator = std.testing.allocator; | ||||
|     const service = "s3"; | ||||
| 
 | ||||
|     const endpoint = try endpointForRequest(allocator, service, request, options); | ||||
|     defer endpoint.deinit(); | ||||
|     try std.testing.expectEqualStrings("https://bucket.s3.us-east-2.amazonaws.com", endpoint.uri); | ||||
|     try std.testing.expectEqualStrings("/key", endpoint.path); | ||||
| } | ||||
|  |  | |||
		Loading…
	
	Add table
		
		Reference in a new issue