Skip to content

Instantly share code, notes, and snippets.

@epfremmer
Created June 6, 2015 17:42
Show Gist options
  • Select an option

  • Save epfremmer/764cb998a18281f6c6b9 to your computer and use it in GitHub Desktop.

Select an option

Save epfremmer/764cb998a18281f6c6b9 to your computer and use it in GitHub Desktop.
{% extends 'WebProfilerBundle:Profiler:layout.html.twig' %}
{% from _self import prettify %}
{% block toolbar %}
{% set request_status_code_color = (collector.getFailedCount > 0) ? 'red' : 'green' %}
{% set icon %}
<img width="28" height="28" alt="Retrofit"
src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABwAAAAcCAQAAADYBBcfAAACvElEQVR42tVTbUhTYRTerDCnKVoUUr/KCZmypA9Koet0bXNLJ5XazDJ/WFaCUY0pExRZXxYiJgsxWWjkaL+yK+po1gjyR2QfmqWxtBmaBtqWGnabT++c11Fu4l/P4VzOPc95zoHznsNZodIbLDdRcKnc1Bu8DAK45ZsOnykQNMopsNooLxCknb0cDq5vml9FtHiIgpBR0R6iihYyFMTDt2Lg56ObPkI6TMGXSof1EV67IqCwisJSWliFAG/E0CfFIiebdNypcxi/1zgyFiIiZ3sJQr0RQx5frLa6k7SOKRo3oMFNR5t62h3rttKXEOKFqDCxtXNmmBokO2KKTlp3IdWuT2dYRNGKwEXEBCcL172G5FG0aIxC0kR9PBTVH1kkwQn+IqJnCE33EalVzT9GJQS1tAdD3CKicJYFrxqx7W2ejCEdZy1FiC5tZxHhLJKOZaRdQJAyV/YAvDliySALHxmxR4Hqe2iwvaOR/CEuZYJFSgYhVbZRkA8KGdEktrqnqra90NndCdkt77fjIHIhexOrfO6O3bbbOj/rqu5IptgyR3sU93QbOYhquZK4MCDp0Ina/PLsu5JvbCTRaapUdUmIV/RzoMdsk/0hWRNdAvKOmvqlN0drsJbJf1P4YsQ5lGrJeuosiOUgbOC8cto3LfOXTdVd7BqZsQKbse+0jUL6WPcesqs4MNSUTQAxGjwFiC8m3yzmqwHJBWYKBJ9WNqW/dHkpU/osch1Yj5RJfXPfSEe/2UPsN490NPfZG5CKyJmcV5ayHyzy7BMqsXfuHhGK/cjAIeSpR92gehR55D8TcQhDEKJwytBJ4fr4NULvrEM8NszfJPyxDoHYAQ1oPCWmIX4gifmDS/DV2DKeb25FHWr76yEG7/9L4YFPeiQQ4/8LkgJ8Et+NncTCsYqzXAEXa7CWdPZzGWdlyV+vST0JanfPvwAAAABJRU5ErkJggg=="/>
<span class="sf-toolbar-status sf-toolbar-status-{{ request_status_code_color }}">{{ collector.getCommunicationsCount }}</span>
{% endset %}
{% set text %}
<div class="sf-toolbar-info-piece">
<b>Retrofit requests</b>
<span class="sf-toolbar-info"></span>
</div>
<div class="sf-toolbar-info-piece">
<b>Successful</b>
<span class="sf-toolbar-status sf-toolbar-status-green">{{ collector.getCommunicationsCount - collector.getFailedCount }}</span>
</div>
<div class="sf-toolbar-info-piece">
<b>Failed</b>
<span class="sf-toolbar-status sf-toolbar-status-{{ collector.getFailedCount > 0 ? 'red' : '' }}">{{ collector.getFailedCount }}</span>
</div>
{% endset %}
{% include 'WebProfilerBundle:Profiler:toolbar_item.html.twig' with { 'link': profiler_url } %}
{% endblock %}
{% block menu %}
<span class="label">
<span class="icon">
<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACYAAAAcCAQAAACn1QXuAAAD2UlEQVR42p2Ve0zTVxTHKS4+KCBqNomCClgEJJAYkznQQIFaWltAiigsxGUgMy6b45HWV4UKUoP1yaMS0DqniVpngKlEMoMzW2Z0QTf4Ax/bdCzFCpQWq60U+Xp/baG/EoGf3vPH7/b3PffTc++55/w8xg+wji4W3ImDw4S3DgSD5fGhA+wcbRxclqsB+30RnmWcda1JPWn1poj8e3TYlvb/l6edTdSLWvYHgcUIdSwiuduxOOdu/n90WF7350648J+a0ClxYNWECglgahP+OyUOPpm34sDMNt6Ez+QwjniAKSzFgKWTw6L33x/3/yMHzU09l/XKlykj7krlXURNDlsEaVm/a8Fh48trUEEKGY4Zb5SaXUpZH4oROAlKvjijPu9GQfcY6jkOQoBlWIgARCAVVbtNo1rxky9/lqiV/hMmQfwXfRtZQxYVVoItC5aUpO8rDIcvYvUNqcN0n7TfJkyC+5lUdYIH9hlOkn3bCWbVCoJLLX9C9+FZEcoIpj2HYHh9XT92ZbUEFl7XSvfhD2EVI5imFh/DX948+lvWhgAEHL3kBrNhNSOYvImCdSgEb+wbGrmjomCFv46DrWn6hN+2QY6ZDYH8Tt6Dv+c4Yfn9bofbN8ABG/xHjYcMKmNHC0Tw/XOF0Ez3+Vah3BMZ1Ezclaynnm1x8LTDBo7U65Tm0tejrltPwwvzIcQO7EIKFsB3c8uoprAqzZruwQpE1cnpeMVxxZLNc8mFQQy2W9Tb+1xSplbjD18EEvM7sjTjuksp6rXVDBeVN29s5ztjFY1VSILpfJAHZiFkG1lAtyTD+gvZsix5emPSC3flm6v3JGvfxNvn+8zDt/HLFR3XUYI6RFPltERkYFro4j6Itdd5JB6JzaaGBAKUFtorpOsHRNoLveAxU1jRQ6xFQbaVNNFBpICN6YjZ00UpN0swj4KFPK/MtTJBffXKoETk3mouiYw7cmoLpsGzNVFkth+NpTKWgnkjof9MnjOflRYqsy4rfV1udebZatIgHhyB0XmylsyL2VXJjtQReMNWe9uGH5JN3ytMubY6HS7J9HSYTI/L1c9ybQoTQfEwG2HN52p7KixuEQ91PH5wEYkE5sRxUYJaFCCr4g+6o+o2slEMNVHjCYqF+RBjJ87m0OI/2YnvwMVCgnLi2AjCcgQgpGen1Mh1bATSgV4pghGISKKyqT6Gj+CHRUj/grT66sGOp7tIjOpmhGEGqYLxA174DOW4gjZiP6EMn2LWO7pz+O8N2nYcQhGq7v+ITZg3wYcPPghFDKibGUNm3u/qq5hL1PWIxgJEIRZBmE69fQsyes/JMSWb+gAAAABJRU5ErkJggg=="
alt="Retrofit">
</span>
<strong>Retrofit</strong>
<span class="count">
<span>{{ collector.getCommunicationsCount }}</span>
</span>
</span>
{% endblock %}
{% block panel %}
<h3>Requests</h3>
<table>
<thead>
<tr>
<th colspan="4">Request</th>
<th colspan="3">Response</th>
</tr>
<tr>
<th>Timestamp</th>
<th>Method</th>
<th>URL</th>
<th>Body</th>
<th>Timestamp</th>
<th>Body</th>
<th>Elapsed Time</th>
</tr>
</thead>
<tbody>
{% for result in collector.getCommunications %}
<tr>
<td valign="top">{{ result.request.timestamp }}</td>
<td valign="top">{{ result.request.method }}</td>
<td valign="top">{{ result.request.url }}</td>
<td valign="top"><pre>{{ result.request.body }}</pre></td>
<td valign="top">{{ result.response.timestamp }}</td>
<td valign="top"><pre>{{ result.response.body }}</pre></td>
<td valign="top">{{ result.elapsed }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endblock %}
<?php
/**
* File RestAdapterProvider.php
*
* @author Edward Pfremmer <epfremme@nerdery.com>
*/
namespace AppBundle\Subscriber;
use GuzzleHttp\Event\AbstractTransferEvent;
use GuzzleHttp\Event\BeforeEvent;
use GuzzleHttp\Event\CompleteEvent;
use GuzzleHttp\Event\ErrorEvent;
use GuzzleHttp\Event\RequestEvents;
use GuzzleHttp\Event\SubscriberInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\DataCollector\DataCollector;
/**
* RetrofitProfiler
*
* Custom Retrofit profiler for debugging API requests/responses
* in the Symfony web profiler
*
* @author Edward Pfremmer <epfremme@nerdery.com>
* @package AppBundle
*/
class RetrofitProfiler extends DataCollector implements SubscriberInterface
{
/**
* Name of the data collector
*
* @var string
*/
const NAME = 'app.profiler.retrofit';
/**
* Timestamp of the last request
*
* @var integer
*/
private $lastRequestTimestamp;
/**
* Constructor
*/
public function __construct()
{
$this->data['communications'] = [];
$this->data['communicationsCount'] = 0;
$this->data['failedCount'] = 0;
}
/**
* Get the communications
*
* @return array
*/
public function getCommunications()
{
return $this->data['communications'];
}
/**
* Get the number of failed communications
*
* @return array
*/
public function getFailedCount()
{
return $this->data['failedCount'];
}
/**
* Get the total number of communications
*
* @return integer
*/
public function getCommunicationsCount()
{
return $this->data['communicationsCount'];
}
/**
* {@inheritdoc}
*/
public function getEvents()
{
return [
'before' => ['onBefore', RequestEvents::LATE],
'complete' => ['onComplete', RequestEvents::LATE],
'error' => ['onError', RequestEvents::LATE]
];
}
/**
* Begin event listener
*
* @param BeforeEvent $event
*/
public function onBefore(BeforeEvent $event)
{
$this->lastRequestTimestamp = microtime(true);
}
/**
* Complete event listener
*
* @param CompleteEvent $event
*/
public function onComplete(CompleteEvent $event)
{
$this->addCommunication($event);
}
/**
* Error event listener
*
* @param ErrorEvent $event
*/
public function onError(ErrorEvent $event)
{
++$this->data['failedCount'];
$this->addCommunication($event);
}
/**
* Add a communication
*
* @param AbstractTransferEvent $event
*/
private function addCommunication(AbstractTransferEvent $event)
{
if (!$event->getResponse()) {
return;
}
++$this->data['communicationsCount'];
$responseTimestamp = microtime(true);
switch ($event->getRequest()->getHeader('Content-Type')) {
case 'application/json':
$requestBody = json_encode(json_decode((string) $event->getRequest()->getBody()), JSON_PRETTY_PRINT);
break;
case 'application/xml':
$doc = new \DOMDocument();
$doc->loadXml((string) $event->getRequest()->getBody());
$doc->preserveWhiteSpace = false;
$doc->formatOutput = true;
$requestBody = $doc->saveXML();
break;
default:
$requestBody = (string) $event->getRequest()->getBody();
break;
}
switch ($event->getRequest()->getHeader('Content-Type')) {
case 'application/json':
$responseBody = json_encode(json_decode((string) $event->getResponse()->getBody()), JSON_PRETTY_PRINT);
break;
case 'applicaiton/xml':
$doc = new \DOMDocument();
$doc->loadXml((string) $event->getResponse()->getBody());
$doc->preserveWhiteSpace = false;
$doc->formatOutput = true;
$responseBody = $doc->saveXML();
break;
default:
$responseBody = (string) $event->getResponse()->getBody();
break;
}
$this->data['communications'][] = [
'elapsed' => $responseTimestamp - $this->lastRequestTimestamp,
'request' => [
'timestamp' => $this->lastRequestTimestamp,
'method' => $event->getRequest()->getMethod(),
'url' => $event->getRequest()->getUrl(),
'body' => $requestBody
],
'response' => [
'body' => $responseBody,
'timestamp' => $responseTimestamp
]
];
}
/**
* {@inheritdoc}
*/
public function collect(Request $request, Response $response, \Exception $exception = null)
{
// noop
}
/**
* {@inheritdoc}
*/
public function getName()
{
return self::NAME;
}
}
services:
# normally created in the adapter
app.api.github.http:
class: GuzzleHttp\Client
arguments:
-
base_url: %retrofit.service.base_url%
debug: true
defaults: { headers: { Accept: "application/vnd.github.v3+json", Content-Type: "application/json" }, exceptions: false }
emitter: @app.api.http.emitter # emitter for retrofit profiler
verify: false
app.api.subscriber.retrofit_profiler:
class: AppBundle\Subscriber\RetrofitProfiler
tags:
- { name: data_collector, template: "AppBundle:Collector:retrofit", id: "app.profiler.retrofit" }
app.api.http.emitter:
class: GuzzleHttp\Event\Emitter
calls:
- [ attach, [ @app.api.subscriber.retrofit_profiler ] ]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment