Skip to content

Commit

Permalink
Add initial Server Timing response headers
Browse files Browse the repository at this point in the history
See #990
  • Loading branch information
westonruter committed Mar 30, 2018
1 parent ca6a0cd commit e4ab6e0
Show file tree
Hide file tree
Showing 5 changed files with 128 additions and 64 deletions.
1 change: 1 addition & 0 deletions includes/class-amp-autoloader.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ class AMP_Autoloader {
*/
private static $_classmap = array(
'AMP_Theme_Support' => 'includes/class-amp-theme-support',
'AMP_Response_Headers' => 'includes/class-amp-response-headers',
'AMP_Comment_Walker' => 'includes/class-amp-comment-walker',
'AMP_Template_Customizer' => 'includes/admin/class-amp-customizer',
'AMP_Post_Meta_Box' => 'includes/admin/class-amp-post-meta-box',
Expand Down
88 changes: 88 additions & 0 deletions includes/class-amp-response-headers.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
<?php
/**
* Class AMP_Response_Headers
*
* @since 1.0
* @package AMP
*/

/**
* Class AMP_Response_Headers
*/
class AMP_Response_Headers {

/**
* Headers sent (or attempted to be sent).
*
* @since 1.0
* @see AMP_Theme_Support::send_header()
* @var array[]
*/
public static $headers_sent = array();

/**
* Send an HTTP response header.
*
* This largely exists to facilitate unit testing but it also provides a better interface for sending headers.
*
* @since 0.7.0
*
* @param string $name Header name.
* @param string $value Header value.
* @param array $args {
* Args to header().
*
* @type bool $replace Whether to replace a header previously sent. Default true.
* @type int $status_code Status code to send with the sent header.
* }
* @return bool Whether the header was sent.
*/
public static function send_header( $name, $value, $args = array() ) {
$args = array_merge(
array(
'replace' => true,
'status_code' => null,
),
$args
);

self::$headers_sent[] = array_merge( compact( 'name', 'value' ), $args );
if ( headers_sent() ) {
return false;
}

header(
sprintf( '%s: %s', $name, $value ),
$args['replace'],
$args['status_code']
);
return true;
}

/**
* Send Server-Timing header.
*
* @since 1.0
* @todo What is the ordering in Chrome dev tools? What are the colors about?
* @todo Is there a better name standardization?
* @todo Is there a way to indicate nested server timings, so an outer method's own time can be seen separately from the inner method's time?
*
* @param string $name Name.
* @param float $duration Duration. If negative, will be added to microtime( true ). Optional.
* @param string $description Description. Optional.
* @return bool Return value of send_header call.
*/
public static function send_server_timing( $name, $duration = null, $description = null ) {
$value = $name;
if ( isset( $description ) ) {
$value .= sprintf( ';desc=%s', wp_json_encode( $description ) );
}
if ( isset( $duration ) ) {
if ( $duration < 0 ) {
$duration = microtime( true ) + $duration;
}
$value .= sprintf( ';dur=%f', $duration * 1000 );
}
return self::send_header( 'Server-Timing', $value, array( 'replace' => false ) );
}
}
66 changes: 19 additions & 47 deletions includes/class-amp-theme-support.php
Original file line number Diff line number Diff line change
Expand Up @@ -68,22 +68,25 @@ class AMP_Theme_Support {
public static $purged_amp_query_vars = array();

/**
* Headers sent (or attempted to be sent).
* Start time when init was called.
*
* @since 0.7
* @see AMP_Theme_Support::send_header()
* @var array[]
* @since 1.0
* @var float
*/
public static $headers_sent = array();
public static $init_start_time;

/**
* Initialize.
*
* @since 0.7
*/
public static function init() {
if ( ! current_theme_supports( 'amp' ) ) {
return;
}

self::$init_start_time = microtime( true );

self::purge_amp_query_vars();
self::handle_xhr_request();
self::add_temporary_discussion_restrictions();
Expand Down Expand Up @@ -318,45 +321,6 @@ public static function purge_amp_query_vars() {
}
}

/**
* Send an HTTP response header.
*
* This largely exists to facilitate unit testing but it also provides a better interface for sending headers.
*
* @since 0.7.0
*
* @param string $name Header name.
* @param string $value Header value.
* @param array $args {
* Args to header().
*
* @type bool $replace Whether to replace a header previously sent. Default true.
* @type int $status_code Status code to send with the sent header.
* }
* @return bool Whether the header was sent.
*/
public static function send_header( $name, $value, $args = array() ) {
$args = array_merge(
array(
'replace' => true,
'status_code' => null,
),
$args
);

self::$headers_sent[] = array_merge( compact( 'name', 'value' ), $args );
if ( headers_sent() ) {
return false;
}

header(
sprintf( '%s: %s', $name, $value ),
$args['replace'],
$args['status_code']
);
return true;
}

/**
* Hook into a POST form submissions, such as the comment form or some other form submission.
*
Expand All @@ -377,7 +341,7 @@ public static function handle_xhr_request() {
// Send AMP response header.
$origin = wp_validate_redirect( wp_sanitize_redirect( esc_url_raw( self::$purged_amp_query_vars['__amp_source_origin'] ) ) );
if ( $origin ) {
self::send_header( 'AMP-Access-Control-Allow-Source-Origin', $origin, array( 'replace' => true ) );
AMP_Response_Headers::send_header( 'AMP-Access-Control-Allow-Source-Origin', $origin, array( 'replace' => true ) );
}

// Intercept POST requests which redirect.
Expand Down Expand Up @@ -526,8 +490,8 @@ public static function intercept_post_request_redirect( $location ) {
$absolute_location .= '#' . $parsed_location['fragment'];
}

self::send_header( 'AMP-Redirect-To', $absolute_location );
self::send_header( 'Access-Control-Expose-Headers', 'AMP-Redirect-To' );
AMP_Response_Headers::send_header( 'AMP-Redirect-To', $absolute_location );
AMP_Response_Headers::send_header( 'Access-Control-Expose-Headers', 'AMP-Redirect-To' );

wp_send_json_success();
}
Expand Down Expand Up @@ -1000,6 +964,7 @@ public static function start_output_buffering() {
* @see AMP_Theme_Support::start_output_buffering()
*/
public static function finish_output_buffering() {
AMP_Response_Headers::send_server_timing( 'amp_output_buffer', -self::$init_start_time, 'AMP Output Buffer' );
echo self::prepare_response( ob_get_clean() ); // WPCS: xss ok.
}

Expand Down Expand Up @@ -1067,6 +1032,8 @@ public static function prepare_response( $response, $args = array() ) {
$args
);

$dom_parse_start = microtime( true );

/*
* Make sure that <meta charset> is present in output prior to parsing.
* Note that the meta charset is supposed to appear within the first 1024 bytes.
Expand Down Expand Up @@ -1098,8 +1065,11 @@ public static function prepare_response( $response, $args = array() ) {
$dom->documentElement->setAttribute( 'amp', '' );
}

AMP_Response_Headers::send_server_timing( 'amp_dom_parse', -$dom_parse_start, 'AMP DOM Parse' );

$assets = AMP_Content_Sanitizer::sanitize_document( $dom, self::$sanitizer_classes, $args );

$dom_serialize_start = microtime( true );
self::ensure_required_markup( $dom );

// @todo If 'utf-8' is not the blog charset, then we'll need to do some character encoding conversation or "entityification".
Expand Down Expand Up @@ -1145,6 +1115,8 @@ public static function prepare_response( $response, $args = array() ) {
);
}

AMP_Response_Headers::send_server_timing( 'amp_dom_serialize', -$dom_serialize_start, 'AMP DOM Serialize' );

return $response;
}

Expand Down
3 changes: 3 additions & 0 deletions includes/templates/class-amp-content-sanitizer.php
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ public static function sanitize_document( &$dom, $sanitizer_classes, $args ) {
$return_styles = ! empty( $args['return_styles'] );
unset( $args['return_styles'] );
foreach ( $sanitizer_classes as $sanitizer_class => $sanitizer_args ) {
$sanitize_class_start = microtime( true );
if ( ! class_exists( $sanitizer_class ) ) {
/* translators: %s is sanitizer class */
_doing_it_wrong( __METHOD__, sprintf( esc_html__( 'Sanitizer (%s) class does not exist', 'amp' ), esc_html( $sanitizer_class ) ), '0.4.1' );
Expand Down Expand Up @@ -90,6 +91,8 @@ public static function sanitize_document( &$dom, $sanitizer_classes, $args ) {
} else {
$stylesheets = array_merge( $stylesheets, $sanitizer->get_stylesheets() );
}

AMP_Response_Headers::send_server_timing( 'amp_sanitize', -$sanitize_class_start, $sanitizer_class );
}

return compact( 'scripts', 'styles', 'stylesheets' );
Expand Down
34 changes: 17 additions & 17 deletions tests/test-class-amp-theme-support.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ public function tearDown() {
$_SERVER['QUERY_STRING'] = '';
unset( $_SERVER['REQUEST_URI'] );
unset( $_SERVER['REQUEST_METHOD'] );
AMP_Theme_Support::$headers_sent = array();
AMP_Response_Headers::$headers_sent = array();
}

/**
Expand Down Expand Up @@ -349,7 +349,7 @@ public function test_purge_amp_query_vars() {
public function test_handle_xhr_request() {
AMP_Theme_Support::purge_amp_query_vars();
AMP_Theme_Support::handle_xhr_request();
$this->assertEmpty( AMP_Theme_Support::$headers_sent );
$this->assertEmpty( AMP_Response_Headers::$headers_sent );

$_GET['_wp_amp_action_xhr_converted'] = '1';

Expand All @@ -358,22 +358,22 @@ public function test_handle_xhr_request() {
$_SERVER['REQUEST_METHOD'] = 'POST';
AMP_Theme_Support::purge_amp_query_vars();
AMP_Theme_Support::handle_xhr_request();
$this->assertEmpty( AMP_Theme_Support::$headers_sent );
$this->assertEmpty( AMP_Response_Headers::$headers_sent );

// Try home source origin.
$_GET['__amp_source_origin'] = home_url();
$_SERVER['REQUEST_METHOD'] = 'POST';
AMP_Theme_Support::purge_amp_query_vars();
AMP_Theme_Support::handle_xhr_request();
$this->assertCount( 1, AMP_Theme_Support::$headers_sent );
$this->assertCount( 1, AMP_Response_Headers::$headers_sent );
$this->assertEquals(
array(
'name' => 'AMP-Access-Control-Allow-Source-Origin',
'value' => home_url(),
'replace' => true,
'status_code' => null,
),
AMP_Theme_Support::$headers_sent[0]
AMP_Response_Headers::$headers_sent[0]
);
$this->assertEquals( PHP_INT_MAX, has_filter( 'wp_redirect', array( 'AMP_Theme_Support', 'intercept_post_request_redirect' ) ) );
$this->assertEquals( PHP_INT_MAX, has_filter( 'comment_post_redirect', array( 'AMP_Theme_Support', 'filter_comment_post_redirect' ) ) );
Expand Down Expand Up @@ -476,7 +476,7 @@ public function test_intercept_post_request_redirect() {
} );

// Test redirecting to full URL with HTTPS protocol.
AMP_Theme_Support::$headers_sent = array();
AMP_Response_Headers::$headers_sent = array();
ob_start();
AMP_Theme_Support::intercept_post_request_redirect( $url );
$this->assertEquals( '{"success":true}', ob_get_clean() );
Expand All @@ -487,7 +487,7 @@ public function test_intercept_post_request_redirect() {
'replace' => true,
'status_code' => null,
),
AMP_Theme_Support::$headers_sent
AMP_Response_Headers::$headers_sent
);
$this->assertContains(
array(
Expand All @@ -496,11 +496,11 @@ public function test_intercept_post_request_redirect() {
'replace' => true,
'status_code' => null,
),
AMP_Theme_Support::$headers_sent
AMP_Response_Headers::$headers_sent
);

// Test redirecting to non-HTTPS URL.
AMP_Theme_Support::$headers_sent = array();
AMP_Response_Headers::$headers_sent = array();
ob_start();
$url = home_url( '/', 'http' );
AMP_Theme_Support::intercept_post_request_redirect( $url );
Expand All @@ -512,7 +512,7 @@ public function test_intercept_post_request_redirect() {
'replace' => true,
'status_code' => null,
),
AMP_Theme_Support::$headers_sent
AMP_Response_Headers::$headers_sent
);
$this->assertContains(
array(
Expand All @@ -521,11 +521,11 @@ public function test_intercept_post_request_redirect() {
'replace' => true,
'status_code' => null,
),
AMP_Theme_Support::$headers_sent
AMP_Response_Headers::$headers_sent
);

// Test redirecting to host-less location.
AMP_Theme_Support::$headers_sent = array();
AMP_Response_Headers::$headers_sent = array();
ob_start();
AMP_Theme_Support::intercept_post_request_redirect( '/new-location/' );
$this->assertEquals( '{"success":true}', ob_get_clean() );
Expand All @@ -536,11 +536,11 @@ public function test_intercept_post_request_redirect() {
'replace' => true,
'status_code' => null,
),
AMP_Theme_Support::$headers_sent
AMP_Response_Headers::$headers_sent
);

// Test redirecting to scheme-less location.
AMP_Theme_Support::$headers_sent = array();
AMP_Response_Headers::$headers_sent = array();
ob_start();
$url = home_url( '/new-location/' );
AMP_Theme_Support::intercept_post_request_redirect( substr( $url, strpos( $url, ':' ) + 1 ) );
Expand All @@ -552,11 +552,11 @@ public function test_intercept_post_request_redirect() {
'replace' => true,
'status_code' => null,
),
AMP_Theme_Support::$headers_sent
AMP_Response_Headers::$headers_sent
);

// Test redirecting to empty location.
AMP_Theme_Support::$headers_sent = array();
AMP_Response_Headers::$headers_sent = array();
ob_start();
AMP_Theme_Support::intercept_post_request_redirect( '' );
$this->assertEquals( '{"success":true}', ob_get_clean() );
Expand All @@ -567,7 +567,7 @@ public function test_intercept_post_request_redirect() {
'replace' => true,
'status_code' => null,
),
AMP_Theme_Support::$headers_sent
AMP_Response_Headers::$headers_sent
);
}

Expand Down

0 comments on commit e4ab6e0

Please sign in to comment.