2021-05-14 22:12:47 +00:00
|
|
|
//! This module provides a low level http interface for working with AWS
|
|
|
|
//! It also provides an option to operate outside the AWS ecosystem through
|
|
|
|
//! the makeRequest call with a null signingOptions.
|
|
|
|
//!
|
|
|
|
//! Typical usage:
|
|
|
|
//! const client = awshttp.AwsHttp.init(allocator);
|
|
|
|
//! defer client.deinit()
|
|
|
|
//! const result = client.callApi (or client.makeRequest)
|
|
|
|
//! defer result.deinit();
|
2021-05-14 19:37:00 +00:00
|
|
|
const std = @import("std");
|
|
|
|
|
|
|
|
const CN_NORTH_1_HASH = std.hash_map.hashString("cn-north-1");
|
|
|
|
const CN_NORTHWEST_1_HASH = std.hash_map.hashString("cn-northwest-1");
|
|
|
|
const US_ISO_EAST_1_HASH = std.hash_map.hashString("us-iso-east-1");
|
|
|
|
const US_ISOB_EAST_1_HASH = std.hash_map.hashString("us-isob-east-1");
|
|
|
|
|
|
|
|
const httplog = std.log.scoped(.awshttp);
|
|
|
|
|
|
|
|
pub const AwsError = error{
|
|
|
|
AddHeaderError,
|
|
|
|
AlpnError,
|
|
|
|
CredentialsError,
|
|
|
|
HttpClientConnectError,
|
|
|
|
HttpRequestError,
|
|
|
|
SignableError,
|
|
|
|
SigningInitiationError,
|
|
|
|
TlsError,
|
|
|
|
RequestCreateError,
|
|
|
|
SetupConnectionError,
|
|
|
|
StatusCodeError,
|
|
|
|
SetRequestMethodError,
|
|
|
|
SetRequestPathError,
|
|
|
|
};
|
|
|
|
|
|
|
|
pub const Options = struct {
|
|
|
|
region: []const u8 = "aws-global",
|
|
|
|
dualstack: bool = false,
|
2021-06-09 23:25:49 +00:00
|
|
|
sigv4_service_name: ?[]const u8 = null,
|
2021-05-14 19:37:00 +00:00
|
|
|
};
|
|
|
|
|
2021-06-09 23:25:49 +00:00
|
|
|
const SigningOptions = struct {
|
2021-05-14 19:37:00 +00:00
|
|
|
region: []const u8 = "aws-global",
|
|
|
|
service: []const u8,
|
|
|
|
};
|
|
|
|
|
2021-08-12 21:24:24 +00:00
|
|
|
pub const HttpRequest = struct {
|
2021-07-24 07:22:06 +00:00
|
|
|
path: []const u8 = "/",
|
|
|
|
query: []const u8 = "",
|
|
|
|
body: []const u8 = "",
|
|
|
|
method: []const u8 = "POST",
|
2021-08-12 21:24:24 +00:00
|
|
|
content_type: []const u8 = "application/json", // Can we get away with this?
|
|
|
|
headers: []Header = &[_]Header{},
|
2021-07-24 07:22:06 +00:00
|
|
|
};
|
2021-08-12 21:24:24 +00:00
|
|
|
pub const HttpResult = struct {
|
2021-05-14 22:12:47 +00:00
|
|
|
response_code: u16, // actually 3 digits can fit in u10
|
2021-05-14 19:37:00 +00:00
|
|
|
body: []const u8,
|
2021-07-24 07:23:06 +00:00
|
|
|
headers: []Header,
|
2021-12-23 16:51:48 +00:00
|
|
|
allocator: std.mem.Allocator,
|
2021-06-18 21:06:58 +00:00
|
|
|
|
2021-05-14 19:37:00 +00:00
|
|
|
pub fn deinit(self: HttpResult) void {
|
2021-06-18 21:06:58 +00:00
|
|
|
self.allocator.free(self.body);
|
2021-07-24 07:23:06 +00:00
|
|
|
for (self.headers) |h| {
|
|
|
|
self.allocator.free(h.name);
|
|
|
|
self.allocator.free(h.value);
|
|
|
|
}
|
|
|
|
self.allocator.free(self.headers);
|
2021-05-14 19:37:00 +00:00
|
|
|
httplog.debug("http result deinit complete", .{});
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2021-08-12 21:24:24 +00:00
|
|
|
pub const Header = struct {
|
2021-05-14 19:37:00 +00:00
|
|
|
name: []const u8,
|
|
|
|
value: []const u8,
|
|
|
|
};
|
|
|
|
|
|
|
|
const EndPoint = struct {
|
|
|
|
uri: []const u8,
|
|
|
|
host: []const u8,
|
|
|
|
scheme: []const u8,
|
|
|
|
port: u16,
|
2021-12-23 16:51:48 +00:00
|
|
|
allocator: std.mem.Allocator,
|
2021-05-14 19:37:00 +00:00
|
|
|
|
|
|
|
fn deinit(self: EndPoint) void {
|
|
|
|
self.allocator.free(self.uri);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
pub const AwsHttp = struct {
|
2021-12-23 16:51:48 +00:00
|
|
|
allocator: std.mem.Allocator,
|
2021-05-14 19:37:00 +00:00
|
|
|
|
|
|
|
const Self = @This();
|
|
|
|
|
2021-12-23 16:51:48 +00:00
|
|
|
pub fn init(allocator: std.mem.Allocator) Self {
|
2021-05-14 19:37:00 +00:00
|
|
|
return .{
|
|
|
|
.allocator = allocator,
|
2022-01-11 05:49:59 +00:00
|
|
|
// .credentialsProvider = // creds provider could be useful
|
2021-05-14 19:37:00 +00:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn deinit(self: *AwsHttp) void {
|
|
|
|
httplog.debug("Deinit complete", .{});
|
|
|
|
}
|
|
|
|
|
2021-05-14 22:12:47 +00:00
|
|
|
/// callApi allows the calling of AWS APIs through a higher-level interface.
|
|
|
|
/// It will calculate the appropriate endpoint and action parameters for the
|
|
|
|
/// service called, and will set up the signing options. The return
|
|
|
|
/// value is simply a raw HttpResult
|
2021-07-24 07:22:06 +00:00
|
|
|
pub fn callApi(self: Self, service: []const u8, request: HttpRequest, options: Options) !HttpResult {
|
2021-05-14 19:37:00 +00:00
|
|
|
const endpoint = try regionSubDomain(self.allocator, service, options.region, options.dualstack);
|
|
|
|
defer endpoint.deinit();
|
2021-06-14 23:12:55 +00:00
|
|
|
httplog.debug("Calling endpoint {s}", .{endpoint.uri});
|
2021-05-14 19:37:00 +00:00
|
|
|
const signing_options: SigningOptions = .{
|
|
|
|
.region = options.region,
|
2021-06-09 23:25:49 +00:00
|
|
|
.service = if (options.sigv4_service_name) |name| name else service,
|
2021-05-14 19:37:00 +00:00
|
|
|
};
|
2021-07-24 07:22:06 +00:00
|
|
|
return try self.makeRequest(endpoint, request, signing_options);
|
2021-05-14 19:37:00 +00:00
|
|
|
}
|
|
|
|
|
2021-05-14 22:12:47 +00:00
|
|
|
/// makeRequest is a low level http/https function that can be used inside
|
|
|
|
/// or outside the context of AWS services. To use it outside AWS, simply
|
|
|
|
/// pass a null value in for signing_options.
|
|
|
|
///
|
|
|
|
/// Otherwise, it will simply take a URL endpoint (without path information),
|
|
|
|
/// HTTP method (e.g. GET, POST, etc.), and request body.
|
|
|
|
///
|
|
|
|
/// At the moment this does not allow the controlling of headers
|
|
|
|
/// This is likely to change. Current headers are:
|
|
|
|
///
|
|
|
|
/// Accept: application/json
|
|
|
|
/// User-Agent: zig-aws 1.0, Powered by the AWS Common Runtime.
|
|
|
|
/// Content-Type: application/x-www-form-urlencoded
|
|
|
|
/// Content-Length: (length of body)
|
|
|
|
///
|
|
|
|
/// Return value is an HttpResult, which will need the caller to deinit().
|
|
|
|
/// HttpResult currently contains the body only. The addition of Headers
|
|
|
|
/// and return code would be a relatively minor change
|
2021-07-24 07:22:06 +00:00
|
|
|
pub fn makeRequest(self: Self, endpoint: EndPoint, request: HttpRequest, signing_options: ?SigningOptions) !HttpResult {
|
2022-01-11 05:49:59 +00:00
|
|
|
httplog.debug("Path: {s}", .{request.path});
|
|
|
|
httplog.debug("Query: {s}", .{request.query});
|
2021-07-24 07:22:06 +00:00
|
|
|
httplog.debug("Method: {s}", .{request.method});
|
|
|
|
httplog.debug("body length: {d}", .{request.body.len});
|
|
|
|
httplog.debug("Body\n====\n{s}\n====", .{request.body});
|
2021-05-14 19:37:00 +00:00
|
|
|
// End CreateRequest. This should return a struct with a deinit function that can do
|
|
|
|
// destroys, etc
|
|
|
|
|
|
|
|
var context = RequestContext{
|
|
|
|
.allocator = self.allocator,
|
|
|
|
};
|
2021-08-12 21:24:24 +00:00
|
|
|
try self.addHeaders(http_request.?, host, request.body, request.content_type, request.headers);
|
2021-05-14 19:37:00 +00:00
|
|
|
if (signing_options) |opts| try self.signRequest(http_request.?, opts);
|
|
|
|
|
2022-01-11 05:49:59 +00:00
|
|
|
// TODO: make req
|
2021-05-14 19:37:00 +00:00
|
|
|
// TODO: Timeout
|
|
|
|
httplog.debug("request_complete. Response code {d}", .{context.response_code.?});
|
|
|
|
httplog.debug("headers:", .{});
|
|
|
|
for (context.headers.?.items) |h| {
|
|
|
|
httplog.debug(" {s}: {s}", .{ h.name, h.value });
|
|
|
|
}
|
|
|
|
httplog.debug("raw response body:\n{s}", .{context.body});
|
2021-05-14 22:12:47 +00:00
|
|
|
|
|
|
|
// Headers would need to be allocated/copied into HttpResult similar
|
|
|
|
// to RequestContext, so we'll leave this as a later excercise
|
|
|
|
// if it becomes necessary
|
2021-05-14 19:37:00 +00:00
|
|
|
const rc = HttpResult{
|
2021-05-14 22:12:47 +00:00
|
|
|
.response_code = context.response_code.?,
|
2021-05-14 19:37:00 +00:00
|
|
|
.body = final_body,
|
2021-07-24 07:23:06 +00:00
|
|
|
.headers = context.headers.?.toOwnedSlice(),
|
2021-06-18 21:06:58 +00:00
|
|
|
.allocator = self.allocator,
|
2021-05-14 19:37:00 +00:00
|
|
|
};
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
2021-05-14 22:12:47 +00:00
|
|
|
fn signRequest(self: Self, http_request: *c.aws_http_message, options: SigningOptions) !void {
|
|
|
|
const creds = try self.getCredentials();
|
|
|
|
httplog.debug("Signing with access key: {s}", .{c.aws_string_c_str(access_key)});
|
|
|
|
|
2022-01-11 05:49:59 +00:00
|
|
|
// const signing_region = try std.fmt.allocPrintZ(self.allocator, "{s}", .{options.region});
|
|
|
|
// defer self.allocator.free(signing_region);
|
|
|
|
// const signing_service = try std.fmt.allocPrintZ(self.allocator, "{s}", .{options.service});
|
|
|
|
// defer self.allocator.free(signing_service);
|
|
|
|
// const temp_signing_config = c.bitfield_workaround_aws_signing_config_aws{
|
|
|
|
// .algorithm = 0, // .AWS_SIGNING_ALGORITHM_V4, // https://github.com/awslabs/aws-c-auth/blob/ace1311f8ef6ea890b26dd376031bed2721648eb/include/aws/auth/signing_config.h#L38
|
|
|
|
// .config_type = 1, // .AWS_SIGNING_CONFIG_AWS, // https://github.com/awslabs/aws-c-auth/blob/ace1311f8ef6ea890b26dd376031bed2721648eb/include/aws/auth/signing_config.h#L24
|
|
|
|
// .signature_type = 0, // .AWS_ST_HTTP_REQUEST_HEADERS, // https://github.com/awslabs/aws-c-auth/blob/ace1311f8ef6ea890b26dd376031bed2721648eb/include/aws/auth/signing_config.h#L49
|
|
|
|
// .region = c.aws_byte_cursor_from_c_str(@ptrCast([*c]const u8, signing_region)),
|
|
|
|
// .service = c.aws_byte_cursor_from_c_str(@ptrCast([*c]const u8, signing_service)),
|
|
|
|
// .should_sign_header = null,
|
|
|
|
// .should_sign_header_ud = null,
|
|
|
|
// // TODO: S3 does not double uri encode. Also not sure why normalizing
|
|
|
|
// // the path here is a flag - seems like it should always do this?
|
|
|
|
// .flags = c.bitfield_workaround_aws_signing_config_aws_flags{
|
|
|
|
// .use_double_uri_encode = 1,
|
|
|
|
// .should_normalize_uri_path = 1,
|
|
|
|
// .omit_session_token = 1,
|
|
|
|
// },
|
|
|
|
// .signed_body_value = c.aws_byte_cursor_from_c_str(""),
|
|
|
|
// .signed_body_header = 1, // .AWS_SBHT_X_AMZ_CONTENT_SHA256, //or 0 = AWS_SBHT_NONE // https://github.com/awslabs/aws-c-auth/blob/ace1311f8ef6ea890b26dd376031bed2721648eb/include/aws/auth/signing_config.h#L131
|
|
|
|
// .credentials = creds,
|
|
|
|
// .credentials_provider = self.credentialsProvider,
|
|
|
|
// .expiration_in_seconds = 0,
|
|
|
|
// };
|
|
|
|
// return AwsError.SignableError;
|
2021-05-14 22:12:47 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2021-08-12 21:24:24 +00:00
|
|
|
fn addHeaders(self: Self, request: *c.aws_http_message, host: []const u8, body: []const u8, content_type: []const u8, additional_headers: []Header) !void {
|
2022-01-11 05:49:59 +00:00
|
|
|
// const accept_header = c.aws_http_header{
|
|
|
|
// .name = c.aws_byte_cursor_from_c_str("Accept"),
|
|
|
|
// .value = c.aws_byte_cursor_from_c_str("application/json"),
|
|
|
|
// .compression = 0, // .AWS_HTTP_HEADER_COMPRESSION_USE_CACHE, // https://github.com/awslabs/aws-c-http/blob/ec42882310900f2b414b279fc24636ba4653f285/include/aws/http/request_response.h#L37
|
|
|
|
// };
|
2021-05-14 19:37:00 +00:00
|
|
|
|
2022-01-11 05:49:59 +00:00
|
|
|
// const host_header = c.aws_http_header{
|
|
|
|
// .name = c.aws_byte_cursor_from_c_str("Host"),
|
|
|
|
// .value = c.aws_byte_cursor_from_c_str(@ptrCast([*c]const u8, host)),
|
|
|
|
// .compression = 0, // .AWS_HTTP_HEADER_COMPRESSION_USE_CACHE,
|
|
|
|
// };
|
2021-05-14 19:37:00 +00:00
|
|
|
|
2022-01-11 05:49:59 +00:00
|
|
|
// const user_agent_header = c.aws_http_header{
|
|
|
|
// .name = c.aws_byte_cursor_from_c_str("User-Agent"),
|
|
|
|
// .value = c.aws_byte_cursor_from_c_str("zig-aws 1.0, Powered by the AWS Common Runtime."),
|
|
|
|
// .compression = 0, // .AWS_HTTP_HEADER_COMPRESSION_USE_CACHE,
|
2021-05-14 19:37:00 +00:00
|
|
|
// };
|
|
|
|
|
|
|
|
// AWS *does* seem to care about Content-Type. I don't think this header
|
|
|
|
// will hold for all APIs
|
2021-08-12 21:24:24 +00:00
|
|
|
const c_type = try std.fmt.allocPrintZ(self.allocator, "{s}", .{content_type});
|
|
|
|
defer self.allocator.free(c_type);
|
2021-05-14 19:37:00 +00:00
|
|
|
const content_type_header = c.aws_http_header{
|
|
|
|
.name = c.aws_byte_cursor_from_c_str("Content-Type"),
|
2021-08-12 21:24:24 +00:00
|
|
|
.value = c.aws_byte_cursor_from_c_str(c_type),
|
2021-06-30 16:14:28 +00:00
|
|
|
.compression = 0, // .AWS_HTTP_HEADER_COMPRESSION_USE_CACHE,
|
2021-05-14 19:37:00 +00:00
|
|
|
};
|
|
|
|
|
2021-08-12 21:24:24 +00:00
|
|
|
for (additional_headers) |h| {
|
|
|
|
const name = try std.fmt.allocPrintZ(self.allocator, "{s}", .{h.name});
|
|
|
|
defer self.allocator.free(name);
|
|
|
|
const value = try std.fmt.allocPrintZ(self.allocator, "{s}", .{h.value});
|
|
|
|
defer self.allocator.free(value);
|
|
|
|
const c_header = c.aws_http_header{
|
|
|
|
.name = c.aws_byte_cursor_from_c_str(name),
|
|
|
|
.value = c.aws_byte_cursor_from_c_str(value),
|
|
|
|
.compression = 0, // .AWS_HTTP_HEADER_COMPRESSION_USE_CACHE,
|
|
|
|
};
|
|
|
|
if (c.aws_http_message_add_header(request, c_header) != c.AWS_OP_SUCCESS)
|
|
|
|
return AwsError.AddHeaderError;
|
|
|
|
}
|
|
|
|
|
2021-05-14 19:37:00 +00:00
|
|
|
if (body.len > 0) {
|
2021-06-18 20:48:31 +00:00
|
|
|
const len = try std.fmt.allocPrintZ(self.allocator, "{d}", .{body.len});
|
2021-05-14 19:37:00 +00:00
|
|
|
// This defer seems to work ok, but I'm a bit concerned about why
|
|
|
|
defer self.allocator.free(len);
|
|
|
|
const content_length_header = c.aws_http_header{
|
|
|
|
.name = c.aws_byte_cursor_from_c_str("Content-Length"),
|
|
|
|
.value = c.aws_byte_cursor_from_c_str(@ptrCast([*c]const u8, len)),
|
2021-06-30 16:14:28 +00:00
|
|
|
.compression = 0, // .AWS_HTTP_HEADER_COMPRESSION_USE_CACHE,
|
2021-05-14 19:37:00 +00:00
|
|
|
};
|
|
|
|
if (c.aws_http_message_add_header(request, content_length_header) != c.AWS_OP_SUCCESS)
|
|
|
|
return AwsError.AddHeaderError;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
fn getCredentials(self: Self) !*c.aws_credentials {
|
2021-06-30 16:14:28 +00:00
|
|
|
// const get_async_result =
|
|
|
|
_ = c.aws_credentials_provider_get_credentials(self.credentialsProvider, callback, &callback_results);
|
2021-05-14 19:37:00 +00:00
|
|
|
|
|
|
|
if (credential_result.error_code != c.AWS_ERROR_SUCCESS) {
|
2021-12-23 16:51:48 +00:00
|
|
|
httplog.err("Could not acquire credentials: {s}:{s}", .{ c.aws_error_name(credential_result.error_code), c.aws_error_str(credential_result.error_code) });
|
2021-05-14 19:37:00 +00:00
|
|
|
return AwsError.CredentialsError;
|
|
|
|
}
|
|
|
|
return credential_result.result orelse unreachable;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
fn fullCast(comptime T: type, val: anytype) T {
|
|
|
|
return @ptrCast(T, @alignCast(@alignOf(T), val));
|
|
|
|
}
|
|
|
|
|
2021-12-23 16:51:48 +00:00
|
|
|
fn regionSubDomain(allocator: std.mem.Allocator, service: []const u8, region: []const u8, useDualStack: bool) !EndPoint {
|
2021-05-14 19:37:00 +00:00
|
|
|
const environment_override = std.os.getenv("AWS_ENDPOINT_URL");
|
|
|
|
if (environment_override) |override| {
|
2021-06-18 20:48:31 +00:00
|
|
|
const uri = try allocator.dupeZ(u8, override);
|
2021-05-14 19:37:00 +00:00
|
|
|
return endPointFromUri(allocator, uri);
|
|
|
|
}
|
|
|
|
// 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 (useDualStack) ".dualstack" else "";
|
|
|
|
|
|
|
|
const domain = switch (std.hash_map.hashString(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",
|
|
|
|
};
|
|
|
|
|
2021-06-18 20:48:31 +00:00
|
|
|
const uri = try std.fmt.allocPrintZ(allocator, "https://{s}{s}.{s}.{s}", .{ service, dualstack, realregion, domain });
|
2021-05-14 19:37:00 +00:00
|
|
|
const host = uri["https://".len..];
|
|
|
|
httplog.debug("host: {s}, scheme: {s}, port: {}", .{ host, "https", 443 });
|
|
|
|
return EndPoint{
|
|
|
|
.uri = uri,
|
|
|
|
.host = host,
|
|
|
|
.scheme = "https",
|
|
|
|
.port = 443,
|
|
|
|
.allocator = allocator,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
/// creates an endpoint from a uri string.
|
|
|
|
///
|
|
|
|
/// allocator: Will be used only to construct the EndPoint struct
|
|
|
|
/// uri: string constructed in such a way that deallocation is needed
|
2021-12-23 16:51:48 +00:00
|
|
|
fn endPointFromUri(allocator: std.mem.Allocator, uri: []const u8) !EndPoint {
|
2021-05-14 19:37:00 +00:00
|
|
|
var scheme: []const u8 = "";
|
|
|
|
var host: []const u8 = "";
|
|
|
|
var port: u16 = 443;
|
|
|
|
var host_start: usize = 0;
|
|
|
|
var host_end: usize = 0;
|
|
|
|
for (uri) |ch, i| {
|
|
|
|
switch (ch) {
|
|
|
|
':' => {
|
|
|
|
if (!std.mem.eql(u8, scheme, "")) {
|
|
|
|
// here to end is port - this is likely a bug if ipv6 address used
|
|
|
|
const rest_of_uri = uri[i + 1 ..];
|
|
|
|
port = try std.fmt.parseUnsigned(u16, rest_of_uri, 10);
|
|
|
|
host_end = i;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
'/' => {
|
|
|
|
if (host_start == 0) {
|
|
|
|
host_start = i + 2;
|
|
|
|
scheme = uri[0 .. i - 1];
|
|
|
|
if (std.mem.eql(u8, scheme, "http")) {
|
|
|
|
port = 80;
|
|
|
|
} else {
|
|
|
|
port = 443;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
else => continue,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (host_end == 0) {
|
|
|
|
host_end = uri.len;
|
|
|
|
}
|
|
|
|
host = uri[host_start..host_end];
|
|
|
|
|
|
|
|
httplog.debug("host: {s}, scheme: {s}, port: {}", .{ host, scheme, port });
|
|
|
|
return EndPoint{
|
|
|
|
.uri = uri,
|
|
|
|
.host = host,
|
|
|
|
.scheme = scheme,
|
|
|
|
.allocator = allocator,
|
|
|
|
.port = port,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
const RequestContext = struct {
|
|
|
|
connection: ?*c.aws_http_connection = null,
|
2021-06-09 23:26:42 +00:00
|
|
|
connection_complete: std.atomic.Atomic(bool) = std.atomic.Atomic(bool).init(false),
|
|
|
|
request_complete: std.atomic.Atomic(bool) = std.atomic.Atomic(bool).init(false),
|
2021-05-14 19:37:00 +00:00
|
|
|
return_error: ?AwsError = null,
|
2021-12-23 16:51:48 +00:00
|
|
|
allocator: std.mem.Allocator,
|
2021-05-14 19:37:00 +00:00
|
|
|
body: ?[]const u8 = null,
|
|
|
|
response_code: ?u16 = null,
|
|
|
|
headers: ?std.ArrayList(Header) = null,
|
|
|
|
|
|
|
|
const Self = @This();
|
|
|
|
|
|
|
|
pub fn deinit(self: Self) void {
|
2021-06-18 21:06:58 +00:00
|
|
|
// We're going to leave it to the caller to free the body
|
|
|
|
// if (self.body) |b| self.allocator.free(b);
|
2021-05-14 19:37:00 +00:00
|
|
|
if (self.headers) |hs| {
|
2021-06-18 21:06:58 +00:00
|
|
|
for (hs.items) |h| {
|
2021-05-14 19:37:00 +00:00
|
|
|
// deallocate the copied values
|
|
|
|
self.allocator.free(h.name);
|
|
|
|
self.allocator.free(h.value);
|
|
|
|
}
|
|
|
|
// deallocate the structure itself
|
2021-06-18 21:06:58 +00:00
|
|
|
hs.deinit();
|
2021-05-14 19:37:00 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn appendToBody(self: *Self, fragment: []const u8) !void {
|
|
|
|
var orig_body: []const u8 = "";
|
|
|
|
if (self.body) |b| {
|
2021-07-24 07:23:58 +00:00
|
|
|
orig_body = try self.allocator.dupe(u8, b);
|
2021-06-18 21:06:58 +00:00
|
|
|
self.allocator.free(b);
|
2021-05-14 19:37:00 +00:00
|
|
|
self.body = null;
|
|
|
|
}
|
|
|
|
defer self.allocator.free(orig_body);
|
2021-07-24 07:23:58 +00:00
|
|
|
self.body = try std.fmt.allocPrint(self.allocator, "{s}{s}", .{ orig_body, fragment });
|
2021-05-14 19:37:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
pub fn addHeader(self: *Self, name: []const u8, value: []const u8) !void {
|
|
|
|
if (self.headers == null)
|
|
|
|
self.headers = std.ArrayList(Header).init(self.allocator);
|
|
|
|
|
|
|
|
const name_copy = try self.allocator.dupeZ(u8, name);
|
|
|
|
const value_copy = try self.allocator.dupeZ(u8, value);
|
|
|
|
|
|
|
|
try self.headers.?.append(.{
|
|
|
|
.name = name_copy,
|
|
|
|
.value = value_copy,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
};
|