44 #define CATEGORY "/res/http_media_cache/"
46 #define TEST_URI "test_media_cache"
57 struct timeval expires;
58 const char *status_text;
60 const char *content_type;
65 static char server_uri[512];
67 #define VALIDATE_EXPIRES(test, bucket_file, expected, delta) do { \
68 RAII_VAR(struct ast_bucket_metadata *, metadata, ast_bucket_file_metadata_get((bucket_file), "__actual_expires"), ao2_cleanup); \
70 ast_test_validate(test, metadata != NULL); \
71 ast_test_validate(test, sscanf(metadata->value, "%d", &actual_expires) == 1); \
72 ast_test_status_update(test, "Checking %d >= %d and %d <= %d\n", \
73 (int) ((expected) + (delta)), actual_expires, \
74 (int) ((expected) - (delta)), actual_expires); \
75 ast_test_validate(test, (((expected) + (delta) >= actual_expires) && ((expected) - (delta) <= actual_expires))); \
78 #define VALIDATE_STR_METADATA(test, bucket_file, key, expected) do { \
79 RAII_VAR(struct ast_bucket_metadata *, metadata, ast_bucket_file_metadata_get((bucket_file), (key)), ao2_cleanup); \
80 ast_test_validate(test, metadata != NULL); \
81 ast_test_validate(test, !strcmp(metadata->value, (expected))); \
84 #define SET_OR_APPEND_CACHE_CONTROL(str) do { \
85 if (!ast_str_strlen((str))) { \
86 ast_str_set(&(str), 0, "%s", "cache-control: "); \
88 ast_str_append(&(str), 0, "%s", ", "); \
94 char file_name[64] =
"/tmp/test-media-cache-XXXXXX";
99 int send_file = options.send_file && method == AST_HTTP_GET;
108 fd = mkstemp(file_name);
110 ast_log(LOG_ERROR,
"Unable to open temp file for testing: %s (%d)", strerror(errno), errno);
114 memset(buf, 1,
sizeof(buf));
115 if (write(fd, buf,
sizeof(buf)) !=
sizeof(buf)) {
116 ast_log(LOG_ERROR,
"Failed to write expected number of bytes to pipe\n");
122 fd = open(file_name, 0);
124 ast_log(LOG_ERROR,
"Unable to open temp file for testing: %s (%d)", strerror(errno), errno);
129 if (!ast_strlen_zero(options.content_type)) {
130 ast_str_append(&http_header, 0,
"Content-Type: %s\r\n", options.content_type);
133 if (options.cache_control.maxage) {
134 SET_OR_APPEND_CACHE_CONTROL(cache_control);
135 ast_str_append(&cache_control, 0,
"max-age=%d", options.cache_control.maxage);
138 if (options.cache_control.s_maxage) {
139 SET_OR_APPEND_CACHE_CONTROL(cache_control);
140 ast_str_append(&cache_control, 0,
"s-maxage=%d", options.cache_control.s_maxage);
143 if (options.cache_control.no_cache) {
144 SET_OR_APPEND_CACHE_CONTROL(cache_control);
148 if (options.cache_control.must_revalidate) {
149 SET_OR_APPEND_CACHE_CONTROL(cache_control);
157 if (options.expires.tv_sec) {
162 ast_strftime(tmbuf,
sizeof(tmbuf),
"%a, %d %b %Y %T %z", &now_time);
166 if (!ast_strlen_zero(options.etag)) {
170 for (v = headers; v; v = v->
next) {
171 if (!strcasecmp(v->
name,
"If-None-Match") && !strcasecmp(v->
value, options.etag)) {
179 ast_http_send(ser, method, options.status_code, options.status_text, http_header, NULL, send_file ? fd : 0, 1);
181 ast_http_send(ser, method, 304,
"Not Modified", http_header, NULL, 0, 1);
189 ast_free(cache_control);
194 ast_free(http_header);
195 ast_free(cache_control);
197 ast_http_error(ser, 418,
"I'm a Teapot",
"Please don't ask me to brew coffee.");
203 .description =
"HTTP Media Cache Test URI",
205 .callback = http_callback,
213 memset(&options, 0,
sizeof(options));
218 static void bucket_file_cleanup(
void *obj)
235 info->
name = __func__;
237 info->
summary =
"Test retrieval of a resource with a Content-Type header";
239 "This test covers retrieval of a resource whose URL does not end with\n"
240 "a parseable extension and whose response includes a Content-Type\n"
241 "header that we recognize.";
242 return AST_TEST_NOT_RUN;
247 options.send_file = 1;
248 options.status_code = 200;
249 options.status_text =
"OK";
250 options.content_type =
"audio/wav";
252 snprintf(uri,
sizeof(uri),
"%s/%s", server_uri,
"foo.wav?account_id=1234");
255 ast_test_validate(test, bucket_file != NULL);
257 ast_test_validate(test, !ast_strlen_zero(bucket_file->
path));
258 VALIDATE_STR_METADATA(test, bucket_file,
"ext",
".wav");
260 return AST_TEST_PASS;
270 info->
name = __func__;
272 info->
summary =
"Test retrieval of a resource with a complex URI";
274 "This test covers retrieval of a resource whose URL does not end with\n"
275 "a parseable extension, but the path portion of the URL does end with\n"
276 "parseable extension.";
277 return AST_TEST_NOT_RUN;
282 options.send_file = 1;
283 options.status_code = 200;
284 options.status_text =
"OK";
286 snprintf(uri,
sizeof(uri),
"%s/%s", server_uri,
"foo.wav?account_id=1234");
289 ast_test_validate(test, bucket_file != NULL);
291 ast_test_validate(test, !ast_strlen_zero(bucket_file->
path));
292 VALIDATE_STR_METADATA(test, bucket_file,
"ext",
".wav");
294 return AST_TEST_PASS;
305 info->
name = __func__;
307 info->
summary =
"Test retrieval of a resource with Cache-Control directives that affect staleness";
309 "This test covers retrieval of a resource with the Cache-Control header,\n"
310 "which specifies no-cache and/or must-revalidate.";
311 return AST_TEST_NOT_RUN;
316 snprintf(uri,
sizeof(uri),
"%s/%s", server_uri,
"foo.wav");
318 options.send_file = 1;
319 options.status_code = 200;
320 options.status_text =
"OK";
322 ast_test_status_update(test,
"Testing no-cache...\n");
323 options.cache_control.no_cache = 1;
325 ast_test_validate(test, bucket_file != NULL);
327 bucket_file_cleanup(bucket_file);
329 ast_test_status_update(test,
"Testing no-cache with ETag...\n");
330 options.cache_control.no_cache = 1;
331 options.etag =
"123456789";
333 ast_test_validate(test, bucket_file != NULL);
335 bucket_file_cleanup(bucket_file);
339 ast_test_status_update(test,
"Testing no-cache with max-age...\n");
340 options.cache_control.no_cache = 1;
341 options.cache_control.maxage = 300;
343 ast_test_validate(test, bucket_file != NULL);
344 VALIDATE_EXPIRES(test, bucket_file, now.tv_sec + 300, 3);
346 bucket_file_cleanup(bucket_file);
348 options.cache_control.maxage = 0;
349 options.cache_control.no_cache = 0;
351 ast_test_status_update(test,
"Testing must-revalidate...\n");
352 options.cache_control.must_revalidate = 1;
354 ast_test_validate(test, bucket_file != NULL);
356 bucket_file_cleanup(bucket_file);
358 ast_test_status_update(test,
"Testing must-revalidate with ETag...\n");
359 options.cache_control.must_revalidate = 1;
360 options.etag =
"123456789";
362 ast_test_validate(test, bucket_file != NULL);
364 bucket_file_cleanup(bucket_file);
368 ast_test_status_update(test,
"Testing must-revalidate with max-age...\n");
369 options.cache_control.must_revalidate = 1;
370 options.cache_control.maxage = 300;
372 ast_test_validate(test, bucket_file != NULL);
373 VALIDATE_EXPIRES(test, bucket_file, now.tv_sec + 300, 3);
376 return AST_TEST_PASS;
387 info->
name = __func__;
389 info->
summary =
"Test retrieval of a resource with age specifiers in Cache-Control";
391 "This test covers retrieval of a resource with the Cache-Control header,\n"
392 "which specifies max-age and/or s-maxage. The test verifies proper precedence\n"
393 "ordering of the header attributes, along with its relation if the Expires\n"
394 "header is present.";
395 return AST_TEST_NOT_RUN;
400 snprintf(uri,
sizeof(uri),
"%s/%s", server_uri,
"foo.wav");
402 options.send_file = 1;
403 options.status_code = 200;
404 options.status_text =
"OK";
406 ast_test_status_update(test,
"Testing max-age...\n");
407 options.cache_control.maxage = 300;
409 ast_test_validate(test, bucket_file != NULL);
410 VALIDATE_EXPIRES(test, bucket_file, now.tv_sec + 300, 3);
412 bucket_file_cleanup(bucket_file);
414 ast_test_status_update(test,
"Testing s-maxage...\n");
416 options.cache_control.maxage = 0;
417 options.cache_control.s_maxage = 300;
419 ast_test_validate(test, bucket_file != NULL);
420 VALIDATE_EXPIRES(test, bucket_file, now.tv_sec + 300, 3);
422 bucket_file_cleanup(bucket_file);
424 ast_test_status_update(test,
"Testing max-age and s-maxage...\n");
426 options.cache_control.maxage = 300;
427 options.cache_control.s_maxage = 600;
429 ast_test_validate(test, bucket_file != NULL);
430 VALIDATE_EXPIRES(test, bucket_file, now.tv_sec + 600, 3);
432 bucket_file_cleanup(bucket_file);
434 ast_test_status_update(test,
"Testing max-age and Expires...\n");
436 options.cache_control.maxage = 300;
437 options.cache_control.s_maxage = 0;
438 options.expires.tv_sec = now.tv_sec + 3000;
440 ast_test_validate(test, bucket_file != NULL);
441 VALIDATE_EXPIRES(test, bucket_file, now.tv_sec + 300, 3);
443 bucket_file_cleanup(bucket_file);
445 ast_test_status_update(test,
"Testing s-maxage and Expires...\n");
447 options.cache_control.maxage = 0;
448 options.cache_control.s_maxage = 300;
449 options.expires.tv_sec = now.tv_sec + 3000;
451 ast_test_validate(test, bucket_file != NULL);
452 VALIDATE_EXPIRES(test, bucket_file, now.tv_sec + 300, 3);
454 bucket_file_cleanup(bucket_file);
456 ast_test_status_update(test,
"Testing s-maxage and Expires...\n");
458 options.cache_control.maxage = 0;
459 options.cache_control.s_maxage = 300;
460 options.expires.tv_sec = now.tv_sec + 3000;
462 ast_test_validate(test, bucket_file != NULL);
463 VALIDATE_EXPIRES(test, bucket_file, now.tv_sec + 300, 3);
465 bucket_file_cleanup(bucket_file);
467 ast_test_status_update(test,
"Testing max-age, s-maxage, and Expires...\n");
469 options.cache_control.maxage = 300;
470 options.cache_control.s_maxage = 600;
471 options.expires.tv_sec = now.tv_sec + 3000;
473 ast_test_validate(test, bucket_file != NULL);
474 VALIDATE_EXPIRES(test, bucket_file, now.tv_sec + 600, 3);
477 return AST_TEST_PASS;
488 info->
name = __func__;
490 info->
summary =
"Test retrieval of an expired resource with an ETag";
492 "This test covers a staleness check of a resource with an ETag\n"
493 "that has also expired. It guarantees that even if a resource\n"
494 "is expired, we will still not consider it stale if the resource\n"
495 "has not changed per the ETag value.";
496 return AST_TEST_NOT_RUN;
501 options.send_file = 1;
502 options.status_code = 200;
503 options.status_text =
"OK";
504 options.etag =
"123456789";
505 options.expires.tv_sec = now.tv_sec - 1;
507 snprintf(uri,
sizeof(uri),
"%s/%s", server_uri,
"foo.wav");
510 ast_test_validate(test, bucket_file != NULL);
512 ast_test_validate(test, !ast_strlen_zero(bucket_file->
path));
513 VALIDATE_STR_METADATA(test, bucket_file,
"etag", options.etag);
514 VALIDATE_EXPIRES(test, bucket_file, now.tv_sec - 1, 3);
518 return AST_TEST_PASS;
529 info->
name = __func__;
531 info->
summary =
"Test retrieval with explicit expiration";
533 "This test covers retrieving a resource that has an Expires.\n"
534 "After retrieval of the resource, staleness is checked. With\n"
535 "a non-expired resource, we expect the resource to not be stale.\n"
536 "When the expiration has occurred, we expect the staleness check\n"
538 return AST_TEST_NOT_RUN;
543 options.send_file = 1;
544 options.status_code = 200;
545 options.status_text =
"OK";
546 options.expires.tv_sec = now.tv_sec + 3000;
548 snprintf(uri,
sizeof(uri),
"%s/%s", server_uri,
"foo.wav");
551 ast_test_validate(test, bucket_file != NULL);
553 ast_test_validate(test, !ast_strlen_zero(bucket_file->
path));
554 VALIDATE_EXPIRES(test, bucket_file, now.tv_sec + 3000, 3);
559 bucket_file_cleanup(bucket_file);
561 options.expires.tv_sec = now.tv_sec - 1;
563 ast_test_validate(test, bucket_file != NULL);
564 VALIDATE_EXPIRES(test, bucket_file, now.tv_sec - 1, 3);
568 return AST_TEST_PASS;
579 info->
name = __func__;
581 info->
summary =
"Test retrieval with an ETag";
583 "This test covers retrieving a resource that has an ETag.\n"
584 "After retrieval of the resource, staleness is checked. With\n"
585 "matching ETags, we expect the resource to not be stale. When\n"
586 "the ETag does not match, we expect the resource to be stale.";
587 return AST_TEST_NOT_RUN;
592 options.send_file = 1;
593 options.status_code = 200;
594 options.status_text =
"OK";
595 options.etag =
"123456789";
597 snprintf(uri,
sizeof(uri),
"%s/%s", server_uri,
"foo.wav");
600 ast_test_validate(test, bucket_file != NULL);
602 ast_test_validate(test, !ast_strlen_zero(bucket_file->
path));
603 VALIDATE_STR_METADATA(test, bucket_file,
"etag", options.etag);
604 VALIDATE_EXPIRES(test, bucket_file, now.tv_sec, 3);
608 options.etag =
"99999999";
611 return AST_TEST_PASS;
622 info->
name = __func__;
624 info->
summary =
"Test nominal retrieval";
626 "Test nominal retrieval of a resource.";
627 return AST_TEST_NOT_RUN;
632 options.send_file = 1;
633 options.status_code = 200;
634 options.status_text =
"OK";
636 snprintf(uri,
sizeof(uri),
"%s/%s", server_uri,
"foo.wav");
639 ast_test_validate(test, bucket_file != NULL);
641 ast_test_validate(test, !ast_strlen_zero(bucket_file->
path));
642 VALIDATE_EXPIRES(test, bucket_file, now.tv_sec, 3);
644 return AST_TEST_PASS;
655 info->
name = __func__;
657 info->
summary =
"Test nominal creation";
659 "Test nominal creation of a resource.";
660 return AST_TEST_NOT_RUN;
665 options.send_file = 1;
666 options.status_code = 200;
667 options.status_text =
"OK";
669 snprintf(uri,
sizeof(uri),
"%s/%s", server_uri,
"foo.wav");
672 ast_test_validate(test, bucket_file != NULL);
675 VALIDATE_EXPIRES(test, bucket_file, now.tv_sec, 3);
677 return AST_TEST_PASS;
685 const char *bindaddr;
686 const char *bindport;
691 if (!config || config == CONFIG_STATUS_FILEINVALID) {
693 }
else if (config == CONFIG_STATUS_FILEUNCHANGED) {
717 snprintf(server_uri,
sizeof(server_uri),
"http://%s:%s%s/%s", bindaddr, bindport,
S_OR(prefix,
""), TEST_URI);
724 static int reload_module(
void)
729 static int load_module(
void)
739 AST_TEST_REGISTER(create_nominal);
741 AST_TEST_REGISTER(retrieve_nominal);
742 AST_TEST_REGISTER(retrieve_etag);
743 AST_TEST_REGISTER(retrieve_expires);
744 AST_TEST_REGISTER(retrieve_etag_expired);
745 AST_TEST_REGISTER(retrieve_cache_control_age);
746 AST_TEST_REGISTER(retrieve_cache_control_directives);
747 AST_TEST_REGISTER(retrieve_parsed_uri);
748 AST_TEST_REGISTER(retrieve_content_type);
750 ast_test_register_init(
CATEGORY, pre_test_cb);
755 static int unload_module(
void)
759 AST_TEST_UNREGISTER(create_nominal);
761 AST_TEST_UNREGISTER(retrieve_nominal);
762 AST_TEST_UNREGISTER(retrieve_etag);
763 AST_TEST_UNREGISTER(retrieve_expires);
764 AST_TEST_UNREGISTER(retrieve_etag_expired);
765 AST_TEST_UNREGISTER(retrieve_cache_control_age);
766 AST_TEST_UNREGISTER(retrieve_cache_control_directives);
767 AST_TEST_UNREGISTER(retrieve_parsed_uri);
768 AST_TEST_UNREGISTER(retrieve_content_type);
773 AST_MODULE_INFO(
ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT,
"HTTP Media Cache Backend Tests",
774 .support_level = AST_MODULE_SUPPORT_CORE,
776 .reload = reload_module,
777 .unload = unload_module,
778 .requires =
"res_http_media_cache",
struct ast_variable * next
Contains all the initialization information required to store a new test definition.
const char * summary
Short summary of test.
Asterisk main include file. File version handling, generic pbx functions.
void ast_http_error(struct ast_tcptls_session_instance *ser, int status, const char *title, const char *text)
Send HTTP error message and close socket.
int ast_http_uri_link(struct ast_http_uri *urihandler)
Register a URI handler.
char * ast_str_buffer(const struct ast_str *buf)
Returns the string buffer within the ast_str buf.
struct ast_bucket_file * ast_bucket_file_retrieve(const char *uri)
Retrieve a bucket file.
struct ast_tm * ast_localtime(const struct timeval *timep, struct ast_tm *p_tm, const char *zone)
Timezone-independent version of localtime_r(3).
static int process_config(int reload)
Load (or reload) configuration.
Structure for variables, used for configurations and for channel variables.
void ast_http_uri_unlink(struct ast_http_uri *urihandler)
Unregister a URI handler.
int ast_str_append(struct ast_str **buf, ssize_t max_len, const char *fmt,...)
Append to a thread local dynamic string.
struct timeval ast_tvnow(void)
Returns current timeval. Meant to replace calls to gettimeofday().
struct ast_bucket_file * ast_bucket_file_alloc(const char *uri)
Allocate a new bucket file.
void ast_http_send(struct ast_tcptls_session_instance *ser, enum ast_http_method method, int status_code, const char *status_title, struct ast_str *http_header, struct ast_str *out, int fd, unsigned int static_content)
Generic function for sending HTTP/1.1 response.
Support for Private Asterisk HTTP Servers.
#define ast_config_load(filename, flags)
Load a config file.
#define ao2_ref(o, delta)
Reference/unreference an object and return the old refcount.
int ast_bucket_file_delete(struct ast_bucket_file *file)
Delete a bucket file from backend storage.
int ast_bucket_file_is_stale(struct ast_bucket_file *file)
Retrieve whether or not the backing datastore views the bucket file as stale.
const char * ast_sorcery_object_get_id(const void *object)
Get the unique identifier of a sorcery object.
const char * description
More detailed description of test.
Bucket file structure, contains reference to file and information about it.
const char * name
name of test, unique to category
describes a server instance
Support for dynamic strings.
const char * category
test category
Module has failed to load, may be in an inconsistent state.
int ast_strftime(char *buf, size_t len, const char *format, const struct ast_tm *tm)
Special version of strftime(3) that handles fractions of a second. Takes the same arguments as strfti...
int ast_bucket_file_temporary_create(struct ast_bucket_file *file)
Common file snapshot creation callback for creating a temporary file.
Structure used to handle boolean flags.
size_t ast_str_strlen(const struct ast_str *buf)
Returns the current length of the string stored within buf.
static int enabled
Whether or not we are storing history.
#define S_OR(a, b)
returns the equivalent of logic or for strings: first one if not empty, otherwise second one...
int attribute_pure ast_false(const char *val)
Make sure something is false. Determine if a string containing a boolean value is "false"...
Definition of a URI handler.
const char * ast_config_option(struct ast_config *cfg, const char *cat, const char *var)
Retrieve a configuration variable within the configuration set.
#define AST_TEST_DEFINE(hdr)
ast_http_method
HTTP Request methods known by Asterisk.
void ast_config_destroy(struct ast_config *cfg)
Destroys a config.
#define ASTERISK_GPL_KEY
The text the key() function should return.
Asterisk module definitions.
#define RAII_VAR(vartype, varname, initval, dtor)
Declare a variable that will call a destructor function when it goes out of scope.
void ast_http_request_close_on_completion(struct ast_tcptls_session_instance *ser)
Request the HTTP connection be closed after this HTTP request.
char path[PATH_MAX]
Local path to this file.
int ast_bucket_file_create(struct ast_bucket_file *file)
Create a new bucket file in backend storage.
#define ast_str_create(init_len)
Create a malloc'ed dynamic length string.