@@ -386,14 +386,15 @@ Dash <- R6::R6Class(
386386
387387 if (! private $ debug && has_fingerprint ) {
388388 response $ status <- 200L
389- response $ set_header (' Cache-Control' ,
390- sprintf(' public, max-age=%s' ,
391- 31536000 ) # 1 year
389+ response $ append_header (' Cache-Control' ,
390+ sprintf(' public, max-age=%s' ,
391+ ' 31536000' ) # 1 year
392392 )
393393 } else if (! private $ debug && ! has_fingerprint ) {
394394 modified <- as.character(as.integer(file.mtime(dep_path )))
395395
396- response $ set_header(' ETag' , modified )
396+ response $ append_header(' ETag' ,
397+ modified )
397398
398399 request_etag <- request $ get_header(' If-None-Match' )
399400
@@ -480,9 +481,9 @@ Dash <- R6::R6Class(
480481 file.size(asset_path ))
481482 close(file_handle )
482483
483- response $ set_header (' Cache-Control' ,
484- sprintf(' public, max-age=%s' ,
485- ' 31536000' )
484+ response $ append_header (' Cache-Control' ,
485+ sprintf(' public, max-age=%s' ,
486+ ' 31536000' )
486487 )
487488 response $ type <- ' image/x-icon'
488489 response $ status <- 200L
@@ -831,9 +832,46 @@ Dash <- R6::R6Class(
831832 if (is.null(private $ callback_context_ )) {
832833 warning(" callback_context is undefined; callback_context may only be accessed within a callback." )
833834 }
835+
834836 private $ callback_context_
835837 },
836838
839+ # ------------------------------------------------------------------------
840+ # request and return callback timing data
841+ # ------------------------------------------------------------------------
842+ # ' @description
843+ # ' Records timing information for a server resource.
844+ # ' @details
845+ # ' The `callback_context.record_timing` method permits retrieving the
846+ # ' duration required to execute a given callback. It may only be called
847+ # ' from within a callback; a warning will be thrown and the method will
848+ # ' otherwise return `NULL` if invoked outside of a callback.
849+ # '
850+ # ' @param name Character. The name of the resource.
851+ # ' @param duration Numeric. The time in seconds to report. Internally, this is
852+ # ' rounded to the nearest millisecond.
853+ # ' @param description Character. A description of the resource.
854+ # '
855+ callback_context.record_timing = function (name ,
856+ duration = NULL ,
857+ description = NULL ) {
858+ if (is.null(private $ callback_context_ )) {
859+ warning(" callback_context is undefined; callback_context.record_timing may only be accessed within a callback." )
860+ return (NULL )
861+ }
862+
863+ timing_information <- self $ server $ get_data(" timing-information" )
864+
865+ if (name %in% timing_information ) {
866+ stop(paste0(" Duplicate resource name " , name , " found." ), call. = FALSE )
867+ }
868+
869+ timing_information [[name ]] <- list (" dur" = round(duration * 1000 ),
870+ " desc" = description )
871+
872+ self $ server $ set_data(" timing-information" , timing_information )
873+ },
874+
837875 # ------------------------------------------------------------------------
838876 # return asset URLs
839877 # ------------------------------------------------------------------------
@@ -1221,6 +1259,42 @@ Dash <- R6::R6Class(
12211259 self $ config $ silence_routes_logging <- dev_tools_silence_routes_logging
12221260 self $ config $ props_check <- dev_tools_props_check
12231261
1262+ if (private $ debug && self $ config $ ui ) {
1263+ self $ server $ on(' before-request' , function (server , ... ) {
1264+ self $ server $ set_data(" timing-information" , list (
1265+ " __dash_server" = list (
1266+ " dur" = as.numeric(Sys.time()),
1267+ " desc" = NULL
1268+ )
1269+ ))
1270+ })
1271+
1272+ self $ server $ on(' request' , function (server , request , ... ) {
1273+ timing_information <- self $ server $ get_data(' timing-information' )
1274+ dash_total <- timing_information [[' __dash_server' ]]
1275+ timing_information [[' __dash_server' ]][[' dur' ]] <- round((as.numeric(Sys.time()) - dash_total [[' dur' ]]) * 1000 )
1276+
1277+ header_as_string <- list ()
1278+
1279+ for (item in seq_along(timing_information )) {
1280+ header_content <- names(timing_information [item ])
1281+
1282+ if (! is.null(timing_information [[item ]]$ desc )) {
1283+ header_content <- paste0(header_content , ' ;desc="' , timing_information [[item ]]$ desc , ' "' )
1284+ }
1285+
1286+ if (! is.null(timing_information [[item ]]$ dur )) {
1287+ header_content <- paste0(header_content , ' ;dur=' , timing_information [[item ]]$ dur )
1288+ }
1289+
1290+ header_as_string [[item ]] <- header_content
1291+ }
1292+
1293+ request $ response $ append_header(' Server-Timing' ,
1294+ paste0(unlist(header_as_string ), collapse = " , " ))
1295+ })
1296+ }
1297+
12241298 if (hot_reload == TRUE & ! (is.null(source_dir ))) {
12251299 self $ server $ on(' cycle-end' , function (server , ... ) {
12261300 # handle case where assets are not present, since we can still hot reload the app itself
@@ -1327,10 +1401,19 @@ Dash <- R6::R6Class(
13271401
13281402 # reset the timestamp so we're able to determine when the last cycle end occurred
13291403 private $ last_cycle <- as.integer(Sys.time())
1404+
1405+ # flush the context to prepare for the next request cycle
1406+ self $ server $ set_data(" timing-information" , list ())
13301407 })
13311408 } else if (hot_reload == TRUE & is.null(source_dir )) {
13321409 message(" \U {26A0} No source directory information available; hot reloading has been disabled.\n Please ensure that you are loading your Dash for R application using source().\n " )
1333- }
1410+ } else if (hot_reload == FALSE && private $ debug && self $ config $ ui ) {
1411+ self $ server $ on(" cycle-end" , function (server , ... ) {
1412+ # flush the context to prepare for the next request cycle
1413+ self $ server $ set_data(" timing-information" , list ())
1414+ })
1415+ }
1416+
13341417 self $ server $ ignite(block = block , showcase = showcase , ... )
13351418 }
13361419 ),
0 commit comments