From 7ceb24c57d843a5ba7aac4b6cfe5e2064dd16758 Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Sat, 9 Nov 2019 13:57:07 +0100 Subject: implement long polling for /check-payment --- src/backend/taler-merchant-httpd.c | 67 ++++++++++++++++++++++++ src/backend/taler-merchant-httpd.h | 11 ++++ src/backend/taler-merchant-httpd_check-payment.c | 65 +++++++++++++++++++---- src/backend/taler-merchant-httpd_poll-payment.c | 63 ++-------------------- src/lib/testing_api_cmd_check_payment.c | 18 ++----- src/lib/testing_api_cmd_poll_payment.c | 24 +++++++-- 6 files changed, 164 insertions(+), 84 deletions(-) (limited to 'src') diff --git a/src/backend/taler-merchant-httpd.c b/src/backend/taler-merchant-httpd.c index 5daea7b6..324fb73b 100644 --- a/src/backend/taler-merchant-httpd.c +++ b/src/backend/taler-merchant-httpd.c @@ -282,6 +282,73 @@ TMH_compute_pay_key (const char *order_id, } +/** + * Resume processing all suspended connections past timeout. + * + * @param cls unused + */ +static void +do_resume (void *cls) +{ + struct TMH_SuspendedConnection *sc; + + (void) cls; + resume_timeout_task = NULL; + while (1) + { + sc = GNUNET_CONTAINER_heap_peek (resume_timeout_heap); + if (NULL == sc) + return; + if (0 != + GNUNET_TIME_absolute_get_remaining ( + sc->long_poll_timeout).rel_value_us) + break; + GNUNET_assert (sc == + GNUNET_CONTAINER_heap_remove_root (resume_timeout_heap)); + sc->hn = NULL; + GNUNET_assert (GNUNET_YES == + GNUNET_CONTAINER_multihashmap_remove (payment_trigger_map, + &sc->key, + sc)); + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Resuming long polled job due to timeout\n"); + MHD_resume_connection (sc->con); + } + resume_timeout_task = GNUNET_SCHEDULER_add_at (sc->long_poll_timeout, + &do_resume, + NULL); +} + + +/** + * Suspend connection from @a sc until payment has been received. + * + * @param sc connection to suspend + */ +void +TMH_long_poll_suspend (struct TMH_SuspendedConnection *sc) +{ + GNUNET_assert (GNUNET_OK == + GNUNET_CONTAINER_multihashmap_put (payment_trigger_map, + &sc->key, + sc, + GNUNET_CONTAINER_MULTIHASHMAPOPTION_MULTIPLE)); + sc->hn = GNUNET_CONTAINER_heap_insert (resume_timeout_heap, + sc, + sc->long_poll_timeout.abs_value_us); + MHD_suspend_connection (sc->con); + if (NULL != resume_timeout_task) + { + GNUNET_SCHEDULER_cancel (resume_timeout_task); + resume_timeout_task = NULL; + } + sc = GNUNET_CONTAINER_heap_peek (resume_timeout_heap); + resume_timeout_task = GNUNET_SCHEDULER_add_at (sc->long_poll_timeout, + &do_resume, + NULL); +} + + /** * Create a taler://pay/ URI for the given @a con and @a order_id * and @a session_id and @a instance_id. diff --git a/src/backend/taler-merchant-httpd.h b/src/backend/taler-merchant-httpd.h index 53dedcb4..03999aab 100644 --- a/src/backend/taler-merchant-httpd.h +++ b/src/backend/taler-merchant-httpd.h @@ -394,6 +394,7 @@ extern struct GNUNET_TIME_Relative default_pay_deadline; void TMH_trigger_daemon (void); + /** * Compute @a key to use for @a order_id and @a mpub in our * #payment_trigger_map. @@ -408,6 +409,15 @@ TMH_compute_pay_key (const char *order_id, struct GNUNET_HashCode *key); +/** + * Suspend connection from @a sc until payment has been received. + * + * @param sc connection to suspend + */ +void +TMH_long_poll_suspend (struct TMH_SuspendedConnection *sc); + + /** * Create a taler://pay/ URI for the given @a con and @a order_id * and @a session_id and @a instance_id. @@ -424,4 +434,5 @@ TMH_make_taler_pay_uri (struct MHD_Connection *con, const char *session_id, const char *instance_id); + #endif diff --git a/src/backend/taler-merchant-httpd_check-payment.c b/src/backend/taler-merchant-httpd_check-payment.c index fad3fdc6..d2356430 100644 --- a/src/backend/taler-merchant-httpd_check-payment.c +++ b/src/backend/taler-merchant-httpd_check-payment.c @@ -48,7 +48,11 @@ struct CheckPaymentRequestContext */ struct TM_HandlerContext hc; - struct MHD_Connection *connection; + /** + * Entry in the #resume_timeout_heap for this check payment, if we are + * suspended. + */ + struct TMH_SuspendedConnection sc; /** * Which merchant instance is this for? @@ -173,11 +177,25 @@ process_refunds_cb (void *cls, * @return #MHD_YES on success */ static int -send_pay_request (const struct CheckPaymentRequestContext *cprc) +send_pay_request (struct CheckPaymentRequestContext *cprc) { int ret; char *already_paid_order_id = NULL; char *taler_pay_uri; + struct GNUNET_TIME_Relative remaining; + + remaining = GNUNET_TIME_absolute_get_remaining (cprc->sc.long_poll_timeout); + if (0 != remaining.rel_value_us) + { + /* long polling: do not queue a response, suspend connection instead */ + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Suspending /check-payment\n"); + TMH_compute_pay_key (cprc->order_id, + &cprc->mi->pubkey, + &cprc->sc.key); + TMH_long_poll_suspend (&cprc->sc); + return MHD_YES; + } /* Check if resource_id has been paid for in the same session * with another order_id. @@ -199,16 +217,16 @@ send_pay_request (const struct CheckPaymentRequestContext *cprc) GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs); /* Always report on hard error as well to enable diagnostics */ GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs); - return TMH_RESPONSE_reply_internal_error (cprc->connection, + return TMH_RESPONSE_reply_internal_error (cprc->sc.con, TALER_EC_CHECK_PAYMENT_DB_FETCH_ORDER_ERROR, "db error fetching pay session info"); } } - taler_pay_uri = TMH_make_taler_pay_uri (cprc->connection, + taler_pay_uri = TMH_make_taler_pay_uri (cprc->sc.con, cprc->order_id, cprc->session_id, cprc->mi->id); - ret = TMH_RESPONSE_reply_json_pack (cprc->connection, + ret = TMH_RESPONSE_reply_json_pack (cprc->sc.con, MHD_HTTP_OK, "{s:s, s:s, s:b, s:s?}", "taler_pay_uri", taler_pay_uri, @@ -249,7 +267,7 @@ parse_contract_terms (struct CheckPaymentRequestContext *cprc) { GNUNET_break (0); cprc->ret - = TMH_RESPONSE_reply_internal_error (cprc->connection, + = TMH_RESPONSE_reply_internal_error (cprc->sc.con, TALER_EC_CHECK_PAYMENT_DB_FETCH_CONTRACT_TERMS_ERROR, "Merchant database error (contract terms corrupted)"); return GNUNET_SYSERR; @@ -260,7 +278,7 @@ parse_contract_terms (struct CheckPaymentRequestContext *cprc) { GNUNET_break (0); cprc->ret - = TMH_RESPONSE_reply_internal_error (cprc->connection, + = TMH_RESPONSE_reply_internal_error (cprc->sc.con, TALER_EC_CHECK_PAYMENT_FAILED_COMPUTE_PROPOSAL_HASH, "Failed to hash proposal"); return GNUNET_SYSERR; @@ -299,13 +317,13 @@ check_order_and_request_payment (struct CheckPaymentRequestContext *cprc) GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs); /* Always report on hard error as well to enable diagnostics */ GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs); - return TMH_RESPONSE_reply_internal_error (cprc->connection, + return TMH_RESPONSE_reply_internal_error (cprc->sc.con, TALER_EC_CHECK_PAYMENT_DB_FETCH_ORDER_ERROR, "db error fetching order"); } if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) { - return TMH_RESPONSE_reply_not_found (cprc->connection, + return TMH_RESPONSE_reply_not_found (cprc->sc.con, TALER_EC_CHECK_PAYMENT_ORDER_ID_UNKNOWN, "unknown order_id"); } @@ -345,11 +363,13 @@ MH_handler_check_payment (struct TMH_RequestHandler *rh, if (NULL == cprc) { + const char *long_poll_timeout_s; + /* First time here, parse request and check order is known */ cprc = GNUNET_new (struct CheckPaymentRequestContext); cprc->hc.cc = &cprc_cleanup; cprc->ret = GNUNET_SYSERR; - cprc->connection = connection; + cprc->sc.con = connection; cprc->mi = mi; *connection_cls = cprc; @@ -384,6 +404,31 @@ MH_handler_check_payment (struct TMH_RequestHandler *rh, cprc->session_id = MHD_lookup_connection_value (connection, MHD_GET_ARGUMENT_KIND, "session_id"); + long_poll_timeout_s = MHD_lookup_connection_value (connection, + MHD_GET_ARGUMENT_KIND, + "timeout"); + if (NULL != long_poll_timeout_s) + { + unsigned int timeout; + + if (1 != sscanf (long_poll_timeout_s, + "%u", + &timeout)) + { + GNUNET_break_op (0); + return TMH_RESPONSE_reply_bad_request (connection, + TALER_EC_PARAMETER_MALFORMED, + "timeout must be non-negative number"); + } + cprc->sc.long_poll_timeout + = GNUNET_TIME_relative_to_absolute (GNUNET_TIME_relative_multiply ( + GNUNET_TIME_UNIT_SECONDS, + timeout)); + } + else + { + cprc->sc.long_poll_timeout = GNUNET_TIME_UNIT_ZERO_ABS; + } db->preflight (db->cls); qs = db->find_contract_terms (db->cls, &cprc->contract_terms, diff --git a/src/backend/taler-merchant-httpd_poll-payment.c b/src/backend/taler-merchant-httpd_poll-payment.c index 3050267f..165a0b58 100644 --- a/src/backend/taler-merchant-httpd_poll-payment.c +++ b/src/backend/taler-merchant-httpd_poll-payment.c @@ -170,42 +170,6 @@ process_refunds_cb (void *cls, } -/** - * Resume processing all suspended connections past timeout. - * - * @param cls unused - */ -static void -do_resume (void *cls) -{ - struct TMH_SuspendedConnection *sc; - - (void) cls; - resume_timeout_task = NULL; - while (1) - { - sc = GNUNET_CONTAINER_heap_peek (resume_timeout_heap); - if (NULL == sc) - return; - if (0 != - GNUNET_TIME_absolute_get_remaining ( - sc->long_poll_timeout).rel_value_us) - break; - GNUNET_assert (sc == - GNUNET_CONTAINER_heap_remove_root (resume_timeout_heap)); - sc->hn = NULL; - GNUNET_assert (GNUNET_YES == - GNUNET_CONTAINER_multihashmap_remove (payment_trigger_map, - &sc->key, - sc)); - MHD_resume_connection (sc->con); - } - resume_timeout_task = GNUNET_SCHEDULER_add_at (sc->long_poll_timeout, - &do_resume, - NULL); -} - - /** * The client did not yet pay, send it the payment request. * @@ -223,31 +187,13 @@ send_pay_request (struct PollPaymentRequestContext *pprc) remaining = GNUNET_TIME_absolute_get_remaining (pprc->sc.long_poll_timeout); if (0 != remaining.rel_value_us) { - struct TMH_SuspendedConnection *sc; - /* long polling: do not queue a response, suspend connection instead */ + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Suspending /poll-payment\n"); TMH_compute_pay_key (pprc->order_id, &pprc->mi->pubkey, &pprc->sc.key); - GNUNET_assert (GNUNET_OK == - GNUNET_CONTAINER_multihashmap_put (payment_trigger_map, - &pprc->sc.key, - &pprc->sc, - GNUNET_CONTAINER_MULTIHASHMAPOPTION_MULTIPLE)); - pprc->sc.hn = GNUNET_CONTAINER_heap_insert (resume_timeout_heap, - &pprc->sc, - pprc->sc.long_poll_timeout. - abs_value_us); - MHD_suspend_connection (pprc->sc.con); - if (NULL != resume_timeout_task) - { - GNUNET_SCHEDULER_cancel (resume_timeout_task); - resume_timeout_task = NULL; - } - sc = GNUNET_CONTAINER_heap_peek (resume_timeout_heap); - resume_timeout_task = GNUNET_SCHEDULER_add_at (sc->long_poll_timeout, - &do_resume, - NULL); + TMH_long_poll_suspend (&pprc->sc); return MHD_YES; } @@ -276,6 +222,8 @@ send_pay_request (struct PollPaymentRequestContext *pprc) "db error fetching pay session info"); } } + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Sending payment request in /poll-payment\n"); taler_pay_uri = TMH_make_taler_pay_uri (pprc->sc.con, pprc->order_id, pprc->session_id, @@ -451,7 +399,6 @@ MH_handler_poll_payment (struct TMH_RequestHandler *rh, "Merchant database error (contract terms corrupted)"); } } - } /* end of first-time initialization / sanity checks */ diff --git a/src/lib/testing_api_cmd_check_payment.c b/src/lib/testing_api_cmd_check_payment.c index a942c587..2e988d75 100644 --- a/src/lib/testing_api_cmd_check_payment.c +++ b/src/lib/testing_api_cmd_check_payment.c @@ -122,23 +122,15 @@ check_payment_cb (void *cls, cps->cpo = NULL; GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "check payment: expected paid: %s: %d\n", - TALER_TESTING_interpreter_get_current_label ( - cps->is), - cps->expect_paid); - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "check payment: paid: %d\n", - paid); - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "check payment: url: %s\n", + "check payment (%s): expected paid: %d, paid: %d, url: %s\n", + TALER_TESTING_interpreter_get_current_label (cps->is), + cps->expect_paid, + paid, taler_pay_uri); - if (paid != cps->expect_paid) TALER_TESTING_FAIL (cps->is); - if (cps->http_status != http_status) TALER_TESTING_FAIL (cps->is); - TALER_TESTING_interpreter_next (cps->is); } @@ -175,7 +167,7 @@ check_payment_run (void *cls, order_id, NULL, GNUNET_TIME_UNIT_ZERO, - check_payment_cb, + &check_payment_cb, cps); GNUNET_assert (NULL != cps->cpo); } diff --git a/src/lib/testing_api_cmd_poll_payment.c b/src/lib/testing_api_cmd_poll_payment.c index 9cef2211..c6200fb8 100644 --- a/src/lib/testing_api_cmd_poll_payment.c +++ b/src/lib/testing_api_cmd_poll_payment.c @@ -181,6 +181,12 @@ conclude_task (void *cls) TALER_TESTING_interpreter_lookup_command (ppc->is, ppc->start_reference); cps = poll_cmd->cls; + if (NULL != cps->cpo) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Expected /poll/payment to have completed, but it did not!\n"); + TALER_TESTING_FAIL (ppc->is); + } if (cps->http_status != ppc->expected_http_status) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, @@ -238,7 +244,9 @@ poll_payment_cb (void *cls, { struct PollPaymentStartState *cps = cls; - if (MHD_HTTP_OK != http_status) + cps->cpo = NULL; + if ( (MHD_HTTP_OK != http_status) && + (NULL != obj) ) { char *log = json_dumps (obj, JSON_COMPACT); @@ -249,7 +257,14 @@ poll_payment_cb (void *cls, log); free (log); } - cps->cpo = NULL; + else + { + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Poll payment returned %u (%d/%d)\n", + http_status, + paid, + refunded); + } cps->paid = paid; cps->http_status = http_status; cps->refunded = refunded; @@ -301,7 +316,10 @@ poll_payment_start_run (void *cls, GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Polling for order id `%s'\n", order_id); - cps->deadline = GNUNET_TIME_relative_to_absolute (cps->timeout); + /* add 1s grace time to timeout */ + cps->deadline + = GNUNET_TIME_absolute_add (GNUNET_TIME_relative_to_absolute (cps->timeout), + GNUNET_TIME_UNIT_SECONDS); cps->cpo = TALER_MERCHANT_poll_payment (is->ctx, cps->merchant_url, order_id, -- cgit v1.2.3