diff --git a/doc/configuration.txt b/doc/configuration.txt index e1257accf..029b37da8 100644 --- a/doc/configuration.txt +++ b/doc/configuration.txt @@ -13245,13 +13245,17 @@ hex2i Converts a hex string containing two hex digits per input byte to an integer. If the input value cannot be converted, then zero is returned. -http_date([]) +http_date([]) Converts an integer supposed to contain a date since epoch to a string representing this date in a format suitable for use in HTTP header fields. If - an offset value is specified, then it is a number of seconds that is added to - the date before the conversion is operated. This is particularly useful to - emit Date header fields, Expires values in responses when combined with a - positive offset, or Last-Modified values when the offset is negative. + an offset value is specified, then it is added to the date before the + conversion is operated. This is particularly useful to emit Date header fields, + Expires values in responses when combined with a positive offset, or + Last-Modified values when the offset is negative. + If a unit value is specified, then consider the timestamp as either + "s" for seconds (default behavior), "ms" for milliseconds, or "us" for + microseconds since epoch. Offset is assumed to have the same unit as + input timestamp. in_table() Uses the string representation of the input sample to perform a look up in @@ -14062,18 +14066,29 @@ cpu_ns_tot : integer high cpu_calls count, for example when processing many HTTP chunks, and for this reason it is often preferred to log cpu_ns_avg instead. -date([]) : integer +date([, ]) : integer Returns the current date as the epoch (number of seconds since 01/01/1970). - If an offset value is specified, then it is a number of seconds that is added - to the current date before returning the value. This is particularly useful - to compute relative dates, as both positive and negative offsets are allowed. + + If an offset value is specified, then it is added to the current date before + returning the value. This is particularly useful to compute relative dates, + as both positive and negative offsets are allowed. It is useful combined with the http_date converter. + is facultative, and can be set to "s" for seconds (default behavior), + "ms" for milliseconds or "us" for microseconds. + If unit is set, return value is an integer reflecting either seconds, + milliseconds or microseconds since epoch, plus offset. + It is useful when a time resolution of less than a second is needed. + Example : # set an expires header to now+1 hour in every response http-response set-header Expires %[date(3600),http_date] + # set an expires header to now+1 hour in every response, with + # millisecond granularity + http-response set-header Expires %[date(3600000,ms),http_date(0,ms)] + date_us : integer Return the microseconds part of the date (the "second" part is returned by date sample). This sample is coherent with the date sample as it is comes diff --git a/include/proto/sample.h b/include/proto/sample.h index 606dcb62a..f0be3fd20 100644 --- a/include/proto/sample.h +++ b/include/proto/sample.h @@ -45,6 +45,7 @@ struct sample_fetch *find_sample_fetch(const char *kw, int len); struct sample_fetch *sample_fetch_getnext(struct sample_fetch *current, int *idx); struct sample_conv *sample_conv_getnext(struct sample_conv *current, int *idx); int smp_resolve_args(struct proxy *p); +int smp_check_date_unit(struct arg *args, char **err); int smp_expr_output_type(struct sample_expr *expr); int c_none(struct sample *smp); int smp_dup(struct sample *smp); diff --git a/src/http_conv.c b/src/http_conv.c index 93b748c2f..cd93aa966 100644 --- a/src/http_conv.c +++ b/src/http_conv.c @@ -33,10 +33,17 @@ #include #include +static int smp_check_http_date_unit(struct arg *args, struct sample_conv *conv, + const char *file, int line, char **err) +{ + return smp_check_date_unit(args, err); +} /* takes an UINT value on input supposed to represent the time since EPOCH, * adds an optional offset found in args[0] and emits a string representing - * the date in RFC-1123/5322 format. + * the date in RFC-1123/5322 format. If optional unit param in args[1] is + * provided, decode timestamp in milliseconds ("ms") or microseconds("us"), + * and use relevant output date format. */ static int sample_conv_http_date(const struct arg *args, struct sample *smp, void *private) { @@ -44,23 +51,45 @@ static int sample_conv_http_date(const struct arg *args, struct sample *smp, voi const char mon[12][4] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; struct buffer *temp; struct tm *tm; - /* With high numbers, the date returned can be negative, the 55 bits mask prevent this. */ - time_t curr_date = smp->data.u.sint & 0x007fffffffffffffLL; + int sec_frac = 0; + time_t curr_date; /* add offset */ if (args && (args[0].type == ARGT_SINT)) - curr_date += args[0].data.sint; + smp->data.u.sint += args[0].data.sint; + + /* report in milliseconds */ + if (args && args[1].type == ARGT_SINT && args[1].data.sint == TIME_UNIT_MS) { + sec_frac = smp->data.u.sint % 1000; + smp->data.u.sint /= 1000; + } + /* report in microseconds */ + else if (args && args[1].type == ARGT_SINT && args[1].data.sint == TIME_UNIT_US) { + sec_frac = smp->data.u.sint % 1000000; + smp->data.u.sint /= 1000000; + } + + /* With high numbers, the date returned can be negative, the 55 bits mask prevent this. */ + curr_date = smp->data.u.sint & 0x007fffffffffffffLL; tm = gmtime(&curr_date); if (!tm) return 0; temp = get_trash_chunk(); - temp->data = snprintf(temp->area, temp->size - temp->data, - "%s, %02d %s %04d %02d:%02d:%02d GMT", - day[tm->tm_wday], tm->tm_mday, mon[tm->tm_mon], - 1900+tm->tm_year, - tm->tm_hour, tm->tm_min, tm->tm_sec); + if (args && args[1].type == ARGT_SINT && args[1].data.sint != TIME_UNIT_S) { + temp->data = snprintf(temp->area, temp->size - temp->data, + "%s, %02d %s %04d %02d:%02d:%02d.%d GMT", + day[tm->tm_wday], tm->tm_mday, mon[tm->tm_mon], + 1900+tm->tm_year, + tm->tm_hour, tm->tm_min, tm->tm_sec, sec_frac); + } else { + temp->data = snprintf(temp->area, temp->size - temp->data, + "%s, %02d %s %04d %02d:%02d:%02d GMT", + day[tm->tm_wday], tm->tm_mday, mon[tm->tm_mon], + 1900+tm->tm_year, + tm->tm_hour, tm->tm_min, tm->tm_sec); + } smp->data.u.str = *temp; smp->data.type = SMP_T_STR; @@ -328,7 +357,7 @@ static int smp_conv_res_capture(const struct arg *args, struct sample *smp, void /* Note: must not be declared as its list will be overwritten */ static struct sample_conv_kw_list sample_conv_kws = {ILH, { - { "http_date", sample_conv_http_date, ARG1(0,SINT), NULL, SMP_T_SINT, SMP_T_STR}, + { "http_date", sample_conv_http_date, ARG2(0,SINT,STR), smp_check_http_date_unit, SMP_T_SINT, SMP_T_STR}, { "language", sample_conv_q_preferred, ARG2(1,STR,STR), NULL, SMP_T_STR, SMP_T_STR}, { "capture-req", smp_conv_req_capture, ARG1(1,SINT), NULL, SMP_T_STR, SMP_T_STR}, { "capture-res", smp_conv_res_capture, ARG1(1,SINT), NULL, SMP_T_STR, SMP_T_STR}, diff --git a/src/sample.c b/src/sample.c index 98b5d573f..1e4039d27 100644 --- a/src/sample.c +++ b/src/sample.c @@ -2940,14 +2940,60 @@ smp_fetch_env(const struct arg *args, struct sample *smp, const char *kw, void * return 1; } -/* retrieve the current local date in epoch time, and applies an optional offset - * of args[0] seconds. +/* Validates the data unit argument passed to "date" fetch. Argument 1 support an + * optional string representing the unit of the result: "s" for seconds, "ms" for + * milliseconds and "us" for microseconds. + * Returns 0 on error and non-zero if OK. + */ +int smp_check_date_unit(struct arg *args, char **err) +{ + if (args[1].type == ARGT_STR) { + if (strcmp(args[1].data.str.area, "s") == 0) { + args[1].data.sint = TIME_UNIT_S; + } + else if (strcmp(args[1].data.str.area, "ms") == 0) { + args[1].data.sint = TIME_UNIT_MS; + } + else if (strcmp(args[1].data.str.area, "us") == 0) { + args[1].data.sint = TIME_UNIT_US; + } + else { + memprintf(err, "expects 's', 'ms' or 'us', got '%s'", + args[1].data.str.area); + return 0; + } + free(args[1].data.str.area); + args[1].data.str.area = NULL; + args[1].type = ARGT_SINT; + } + else if (args[1].type != ARGT_STOP) { + memprintf(err, "Unexpected arg type"); + return 0; + } + + return 1; +} + +/* retrieve the current local date in epoch time, converts it to milliseconds + * or microseconds if asked to in optional args[1] unit param, and applies an + * optional args[0] offset. */ static int smp_fetch_date(const struct arg *args, struct sample *smp, const char *kw, void *private) { smp->data.u.sint = date.tv_sec; + /* report in milliseconds */ + if (args && args[1].type == ARGT_SINT && args[1].data.sint == TIME_UNIT_MS) { + smp->data.u.sint *= 1000; + smp->data.u.sint += date.tv_usec / 1000; + } + /* report in microseconds */ + else if (args && args[1].type == ARGT_SINT && args[1].data.sint == TIME_UNIT_US) { + smp->data.u.sint *= 1000000; + smp->data.u.sint += date.tv_usec; + } + /* add offset */ if (args && args[0].type == ARGT_SINT) smp->data.u.sint += args[0].data.sint; @@ -3259,7 +3305,7 @@ static struct sample_fetch_kw_list smp_kws = {ILH, { { "always_false", smp_fetch_false, 0, NULL, SMP_T_BOOL, SMP_USE_INTRN }, { "always_true", smp_fetch_true, 0, NULL, SMP_T_BOOL, SMP_USE_INTRN }, { "env", smp_fetch_env, ARG1(1,STR), NULL, SMP_T_STR, SMP_USE_INTRN }, - { "date", smp_fetch_date, ARG1(0,SINT), NULL, SMP_T_SINT, SMP_USE_INTRN }, + { "date", smp_fetch_date, ARG2(0,SINT,STR), smp_check_date_unit, SMP_T_SINT, SMP_USE_INTRN }, { "date_us", smp_fetch_date_us, 0, NULL, SMP_T_SINT, SMP_USE_INTRN }, { "hostname", smp_fetch_hostname, 0, NULL, SMP_T_STR, SMP_USE_INTRN }, { "nbproc", smp_fetch_nbproc,0, NULL, SMP_T_SINT, SMP_USE_INTRN },