1
0
Fork 0
mirror of https://github.com/warmcat/libwebsockets.git synced 2025-03-09 00:00:04 +01:00

http: add RFC7231 date and time helpers and retry-after handling

Teach lws how to deal with date: and retry-after:

Add quick selftest into apt-test-lws_tokenize

Expand lws_retry_sul_schedule_retry_wsi() to check for retry_after and
increase the backoff if a larger one found.

Finally, change SS h1 protocol to handle 503 + retry-after: as a
failure, and apply any increased backoff from retry-after
automatically.
This commit is contained in:
Andy Green 2020-09-17 12:43:31 +01:00
parent 40f4ce9ffc
commit 49e92ba089
9 changed files with 355 additions and 7 deletions

View file

@ -731,6 +731,53 @@ LWS_VISIBLE LWS_EXTERN int
lws_urldecode(char *string, const char *escaped, int len);
///@}
/**
* lws_http_date_render_from_unix() - render unixtime as RFC7231 date string
*
* \param buf: Destination string buffer
* \param len: avilable length of dest string buffer in bytes
* \param t: pointer to the time_t to render
*
* Returns 0 if time_t is rendered into the string buffer successfully, else
* nonzero.
*/
LWS_VISIBLE LWS_EXTERN int
lws_http_date_render_from_unix(char *buf, size_t len, const time_t *t);
/**
* lws_http_date_parse_unix() - parse a RFC7231 date string into unixtime
*
* \param b: Source string buffer
* \param len: avilable length of source string buffer in bytes
* \param t: pointer to the destination time_t to set
*
* Returns 0 if string buffer parsed as RFC7231 time successfully, and
* *t set to the parsed unixtime, else return nonzero.
*/
LWS_VISIBLE LWS_EXTERN int
lws_http_date_parse_unix(const char *b, size_t len, time_t *t);
/**
* lws_http_check_retry_after() - increase a timeout if retry-after present
*
* \param wsi: http stream this relates to
* \param us_interval_in_out: default us retry interval on entry may be updated
*
* This function may extend the incoming retry interval if the server has
* requested that using retry-after: header. It won't reduce the incoming
* retry interval, only leave it alone or increase it.
*
* *us_interval_in_out should be set to a default retry interval on entry, if
* the wsi has a retry-after time or interval that resolves to an interval
* longer than the entry *us_interval_in_out, that will be updated to the longer
* interval and return 0.
*
* If no usable retry-after or the time is now or in the past,
* *us_interval_in_out is left alone and the function returns nonzero.
*/
LWS_VISIBLE LWS_EXTERN int
lws_http_check_retry_after(struct lws *wsi, lws_usec_t *us_interval_in_out);
/**
* lws_return_http_status() - Return simple http status
* \param wsi: Websocket instance (available from user callback)

View file

@ -370,6 +370,8 @@ lws_socket_bind(struct lws_vhost *vhost, lws_sockfd_type sockfd, int port,
return port;
}
#if defined(LWS_WITH_CLIENT)
unsigned int
lws_retry_get_delay_ms(struct lws_context *context,
const lws_retry_bo_t *retry, uint16_t *ctry,
@ -430,10 +432,30 @@ int
lws_retry_sul_schedule_retry_wsi(struct lws *wsi, lws_sorted_usec_list_t *sul,
sul_cb_t cb, uint16_t *ctry)
{
return lws_retry_sul_schedule(wsi->a.context, wsi->tsi, sul,
wsi->retry_policy, cb, ctry);
char conceal;
lws_usec_t us = lws_retry_get_delay_ms(wsi->a.context,
wsi->retry_policy, ctry,
&conceal) * LWS_US_PER_MS;
if (!conceal)
/* if our reties are up, they're up... */
return 1;
#if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2)
if (wsi->role_ops == &role_ops_h1 || wsi->role_ops == &role_ops_h2)
/*
* Since we're doing it by wsi, we're in a position to check for
* http retry-after, it will increase us accordingly if found
*/
lws_http_check_retry_after(wsi, &us);
#endif
lws_sul_schedule(wsi->a.context, wsi->tsi, sul, cb, us);
return 0;
}
#endif
#if defined(LWS_WITH_IPV6)
unsigned long
lws_get_addr_scope(const char *ifname_or_ipaddr)

View file

@ -33,6 +33,7 @@ include_directories(. ./compression)
list(APPEND SOURCES
roles/http/header.c
roles/http/date.c
roles/http/parsers.c)
if (NOT LWS_WITHOUT_SERVER)
@ -87,4 +88,4 @@ endif()
# Keep explicit parent scope exports at end
#
exports_to_parent_scope()
exports_to_parent_scope()

209
lib/roles/http/date.c Normal file
View file

@ -0,0 +1,209 @@
/*
* libwebsockets - small server side websockets and web server implementation
*
* Copyright (C) 2010 - 2020 Andy Green <andy@warmcat.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*
* RFC7231 date string generation and parsing
*/
#include "private-lib-core.h"
/*
* To avoid needless pointers, we encode these in one string using the fact
* they're 3 chars each to index it
*/
static const char *const s =
"JanFebMarAprMayJunJulAugSepOctNovDecMonTueWedThuFriSatSun";
static int
lws_http_date_render(char *buf, size_t len, const struct tm *tm)
{
const char *w = s + 36 + (3 * tm->tm_wday), *m = s + (3 * tm->tm_mon);
if (len < 29)
return -1;
lws_snprintf(buf, len, "%c%c%c, %02d %c%c%c %d %02d:%02d:%02d GMT",
w[0], w[1], w[2], tm->tm_mday, m[0], m[1], m[2],
1900 + tm->tm_year, tm->tm_hour, tm->tm_min, tm->tm_sec);
return 0;
}
int
lws_http_date_render_from_unix(char *buf, size_t len, const time_t *t)
{
struct tm *tm = gmtime(t);
if (lws_http_date_render(buf, len, tm))
return -1;
return 0;
}
static int
lws_http_date_parse(const char *b, size_t len, struct tm *tm)
{
int n;
if (len < 29)
return -1;
/*
* We reject anything that isn't a properly-formatted RFC7231 date, eg
*
* Tue, 15 Nov 1994 08:12:31 GMT
*/
if (b[3] != ',' || b[4] != ' ' || b[7] != ' ' || b[11] != ' ' ||
b[16] != ' ' || b[19] != ':' || b[22] != ':' || b[25] != ' ' ||
b[26] != 'G' || b[27] != 'M' || b[28] != 'T')
return -1;
memset(tm, 0, sizeof(*tm));
for (n = 36; n < 57; n += 3)
if (b[0] == s[n] && b[1] == s[n + 1] && b[2] == s[n + 2])
break;
else
tm->tm_wday++;
if (n == 57)
return -1;
for (n = 0; n < 36; n += 3)
if (b[8] == s[n] && b[9] == s[n + 1] && b[10] == s[n + 2])
break;
else
tm->tm_mon++;
if (n == 36)
return -1;
tm->tm_mday = atoi(b + 5);
n = atoi(b + 12);
if (n < 1900)
return -1;
tm->tm_year = n - 1900;
n = atoi(b + 17);
if (n < 0 || n > 23)
return -1;
tm->tm_hour = n;
n = atoi(b + 20);
if (n < 0 || n > 60)
return -1;
tm->tm_min = n;
n = atoi(b + 23);
if (n < 0 || n > 61) /* leap second */
return -1;
tm->tm_sec = n;
return 0;
}
int
lws_http_date_parse_unix(const char *b, size_t len, time_t *t)
{
struct tm tm;
if (lws_http_date_parse(b, len, &tm))
return -1;
*t = mktime(&tm);
return (int)*t == -1 ? -1 : 0;
}
#if defined(LWS_WITH_CLIENT)
int
lws_http_check_retry_after(struct lws *wsi, lws_usec_t *us_interval_in_out)
{
size_t len = lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_RETRY_AFTER);
char *p = lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP_RETRY_AFTER);
lws_usec_t u;
time_t t, td;
if (!p)
return 1;
/*
* There are two arg styles for RETRY_AFTER specified in RFC7231 7.1.3,
* either a full absolute second-resolution date/time, or an integer
* interval
*
* Retry-After: Fri, 31 Dec 1999 23:59:59 GMT
* Retry-After: 120
*/
if (len < 9)
u = ((lws_usec_t)(time_t)atoi(p)) * LWS_USEC_PER_SEC;
else {
if (lws_http_date_parse_unix(p, len, &t))
return 1;
/*
* If possible, look for DATE from the server as well, so we
* can calculate the interval it thinks it is giving us,
* eliminating problems from server - client clock skew
*/
time(&td);
len = lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_DATE);
if (len) {
p = lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP_DATE);
/* if this fails, it leaves td as client time */
(void)lws_http_date_parse_unix(p, len, &td);
}
if (td >= t)
/*
* if he's effectively giving us a 0 or negative
* interval, just ignore the whole thing and keep the
* incoming interval
*/
return 1;
u = ((lws_usec_t)(t - td)) * LWS_USEC_PER_SEC;
}
/*
* We are only willing to increase the incoming interval, not
* decrease it
*/
if (u < *us_interval_in_out)
/* keep the incoming interval */
return 1;
/* use the computed interval */
*us_interval_in_out = u;
return 0;
}
#endif

View file

@ -330,6 +330,12 @@ lws_http_multipart_headers(struct lws *wsi, uint8_t *p);
int
lws_http_string_to_known_header(const char *s, size_t slen);
int
lws_http_date_render_from_unix(char *buf, size_t len, const time_t *t);
int
lws_http_date_parse_unix(const char *b, size_t len, time_t *t);
enum {
CCTLS_RETURN_ERROR = -1,
CCTLS_RETURN_DONE = 0,

View file

@ -390,6 +390,9 @@ lws_ss_sys_fetch_policy(struct lws_context *context);
lws_ss_state_return_t
lws_ss_event_helper(lws_ss_handle_t *h, lws_ss_constate_t cs);
lws_ss_state_return_t
_lws_ss_backoff(lws_ss_handle_t *h, lws_usec_t us_override);
lws_ss_state_return_t
lws_ss_backoff(lws_ss_handle_t *h);

View file

@ -346,6 +346,7 @@ secstream_h1(struct lws *wsi, enum lws_callback_reasons reason, void *user,
lws_ss_state_return_t r;
int f = 0, m, status;
char conceal_eom = 0;
lws_usec_t inter;
size_t buflen;
switch (reason) {
@ -410,6 +411,24 @@ secstream_h1(struct lws *wsi, enum lws_callback_reasons reason, void *user,
/* it's just telling use we connected / joined the nwsi */
// break;
if (status == HTTP_STATUS_SERVICE_UNAVAILABLE /* 503 */ ) {
/*
* We understand this attempt failed, and that we should
* conceal this attempt. If there's a specified
* retry-after, we should use that if larger than our
* computed backoff
*/
inter = 0;
lws_http_check_retry_after(wsi, &inter);
r = _lws_ss_backoff(h, inter);
if (r != LWSSSSRET_OK)
return _lws_ss_handle_state_ret(r, wsi, &h);
return -1; /* end this stream */
}
if (h->policy->u.http.resp_expect)
h->u.http.good_respcode =
status == h->policy->u.http.resp_expect;

View file

@ -188,7 +188,7 @@ lws_ss_set_timeout_us(lws_ss_handle_t *h, lws_usec_t us)
}
lws_ss_state_return_t
lws_ss_backoff(lws_ss_handle_t *h)
_lws_ss_backoff(lws_ss_handle_t *h, lws_usec_t us_override)
{
uint64_t ms;
char conceal;
@ -208,14 +208,26 @@ lws_ss_backoff(lws_ss_handle_t *h)
return lws_ss_event_helper(h, LWSSSCS_ALL_RETRIES_FAILED);
}
h->seqstate = SSSEQ_RECONNECT_WAIT;
lws_ss_set_timeout_us(h, ms * LWS_US_PER_MS);
/* Only increase our planned backoff, or go with it */
lwsl_info("%s: ss %p: retry wait %"PRIu64"ms\n", __func__, h, ms);
if (us_override < (lws_usec_t)ms * LWS_US_PER_MS)
us_override = ms * LWS_US_PER_MS;
h->seqstate = SSSEQ_RECONNECT_WAIT;
lws_ss_set_timeout_us(h, us_override);
lwsl_info("%s: ss %p: retry wait %dms\n", __func__, h,
(int)(us_override / 1000));
return LWSSSSRET_OK;
}
lws_ss_state_return_t
lws_ss_backoff(lws_ss_handle_t *h)
{
return _lws_ss_backoff(h, 0);
}
#if defined(LWS_WITH_SYS_SMD)
/*

View file

@ -600,6 +600,35 @@ int main(int argc, const char **argv)
printf("\t}\n");
}
#if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2)
{
time_t t;
if (lws_http_date_parse_unix("Tue, 15 Nov 1994 08:12:31 GMT", 29, &t)) {
lwsl_err("%s: date parse failed\n", __func__);
fail++;
} else {
/* lwsl_notice("%s: %llu\n", __func__, (unsigned long long)t); */
if (t != (time_t)784887151) {
lwsl_err("%s: date parse wrong\n", __func__);
fail++;
} else {
char s[30];
if (lws_http_date_render_from_unix(s, sizeof(s), &t)) {
lwsl_err("%s: failed date render\n", __func__);
fail++;
} else {
if (!strcmp(s, "Tue, 15 Nov 1994 08:12:31 GMT")) {
lwsl_err("%s: date render wrong\n", __func__);
fail++;
}
}
}
}
}
#endif
lwsl_user("Completed: PASS: %d, FAIL: %d\n", ok, fail);
return !(ok && !fail);