Allow to disable the sandbox, refactor the configuration. Fix #37

This commit is contained in:
William DURAND 2012-07-18 13:18:26 +02:00
parent 208d25f825
commit bee518f92e
7 changed files with 427 additions and 388 deletions

View File

@ -23,7 +23,13 @@ class Configuration implements ConfigurationInterface
->root('nelmio_api_doc') ->root('nelmio_api_doc')
->children() ->children()
->scalarNode('name')->defaultValue('API documentation')->end() ->scalarNode('name')->defaultValue('API documentation')->end()
->scalarNode('sandbox_target')->defaultValue('/app_dev.php')->end() ->arrayNode('sandbox')
->addDefaultsIfNotSet()
->children()
->scalarNode('enabled')->defaultTrue()->end()
->scalarNode('endpoint')->defaultValue('/app_dev.php')->end()
->end()
->end()
->end(); ->end();
return $treeBuilder; return $treeBuilder;

View File

@ -29,7 +29,8 @@ class NelmioApiDocExtension extends Extension
$config = $processor->processConfiguration($configuration, $configs); $config = $processor->processConfiguration($configuration, $configs);
$container->setParameter('nelmio_api_doc.api_name', $config['name']); $container->setParameter('nelmio_api_doc.api_name', $config['name']);
$container->setParameter('nelmio_api_doc.api_sandbox_target', $config['sandbox_target']); $container->setParameter('nelmio_api_doc.sandbox.enabled', $config['sandbox']['enabled']);
$container->setParameter('nelmio_api_doc.sandbox.endpoint', $config['sandbox']['endpoint']);
$loader = new XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config')); $loader = new XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
$loader->load('formatters.xml'); $loader->load('formatters.xml');

View File

@ -23,7 +23,12 @@ class HtmlFormatter extends AbstractFormatter
/** /**
* @var string * @var string
*/ */
private $sandboxTarget; private $endpoint;
/**
* @var boolean
*/
private $enableSandbox;
/** /**
* @var \Symfony\Component\Templating\EngineInterface * @var \Symfony\Component\Templating\EngineInterface
@ -39,11 +44,19 @@ class HtmlFormatter extends AbstractFormatter
} }
/** /**
* @param string $sandboxTarget * @param string $endpoint
*/ */
public function setSandboxTarget($sandboxTarget) public function setEndpoint($endpoint)
{ {
$this->sandboxTarget = $sandboxTarget; $this->endpoint = $endpoint;
}
/**
* @param boolean $enableSandbox
*/
public function setEnableSandbox($enableSandbox)
{
$this->enableSandbox = $enableSandbox;
} }
/** /**
@ -83,7 +96,8 @@ class HtmlFormatter extends AbstractFormatter
{ {
return array( return array(
'apiName' => $this->apiName, 'apiName' => $this->apiName,
'sandboxTarget' => $this->sandboxTarget, 'endpoint' => $this->endpoint,
'enableSandbox' => $this->enableSandbox,
'date' => date(DATE_RFC822), 'date' => date(DATE_RFC822),
'css' => file_get_contents(__DIR__ . '/../Resources/public/css/screen.css'), 'css' => file_get_contents(__DIR__ . '/../Resources/public/css/screen.css'),
'js' => file_get_contents(__DIR__ . '/../Resources/public/js/all.js'), 'js' => file_get_contents(__DIR__ . '/../Resources/public/js/all.js'),

View File

@ -174,6 +174,15 @@ You can specify your own API name:
nelmio_api_doc: nelmio_api_doc:
name: My API name: My API
This bundle provides a sandbox mode in order to test API methods. You can
configure this sandbox using the following parameters:
# app/config/config.yml
nelmio_api_doc:
sandbox:
enabled: true # default: true, you can set this parameter to `false` to disable the sandbox
endpoint: http://sandbox.example.com/ # default: /app_dev.php, use this parameter to define which URL to call through the sandbox
## Credits ## ## Credits ##

View File

@ -30,8 +30,11 @@
<call method="setApiName"> <call method="setApiName">
<argument>%nelmio_api_doc.api_name%</argument> <argument>%nelmio_api_doc.api_name%</argument>
</call> </call>
<call method="setSandboxTarget"> <call method="setEnableSandbox">
<argument>%nelmio_api_doc.api_sandbox_target%</argument> <argument>%nelmio_api_doc.sandbox.enabled%</argument>
</call>
<call method="setEndpoint">
<argument>%nelmio_api_doc.sandbox.endpoint%</argument>
</call> </call>
</service> </service>
</services> </services>

View File

@ -29,233 +29,235 @@
$(this).next().slideToggle('slow'); $(this).next().slideToggle('slow');
}); });
var toggleButtonText = function ($btn) { {% if enableSandbox %}
if ($btn.text() === 'Default') { var toggleButtonText = function ($btn) {
$btn.text('Raw'); if ($btn.text() === 'Default') {
} else { $btn.text('Raw');
$btn.text('Default');
}
};
var renderRawBody = function ($container) {
var rawData, $btn;
rawData = $container.data('raw-response');
$btn = $container.parents('.pane').find('.to-raw');
$container.addClass('prettyprinted');
$container.html(rawData);
$btn.removeClass('to-raw');
$btn.addClass('to-prettify');
toggleButtonText($btn);
};
var renderPrettifiedBody = function ($container) {
var rawData, $btn;
rawData = $container.data('raw-response');
$btn = $container.parents('.pane').find('.to-prettify');
$container.removeClass('prettyprinted');
$container.html(prettifyResponse(rawData));
prettyPrint && prettyPrint();
$btn.removeClass('to-prettify');
$btn.addClass('to-raw');
toggleButtonText($btn);
};
$('.tabs li').click(function() {
var contentGroup = $(this).parents('.content');
$('.pane.selected', contentGroup).removeClass('selected');
$('.pane.' + $(this).data('pane'), contentGroup).addClass('selected');
$('li', $(this).parent()).removeClass('selected');
$(this).addClass('selected');
});
var prettifyResponse = function(text) {
try {
var data = typeof text === 'string' ? JSON.parse(text) : text;
text = JSON.stringify(data, undefined, ' ');
} catch (err) {
}
// HTML encode the result
return $('<div>').text(text).html();
};
var displayFinalUrl = function(xhr, method, url, container) {
container.html(method + ' ' + url);
};
var displayResponseData = function(xhr, container) {
var data = xhr.responseText;
container.data('raw-response', data);
if ('<' === data[0]) {
renderRawBody(container);
} else { } else {
renderPrettifiedBody(container); $btn.text('Default');
} }
container.parents('.pane').find('.to-prettify').text('Raw');
container.parents('.pane').find('.to-raw').text('Raw');
};
var displayResponseHeaders = function(xhr, container) {
var text = xhr.status + ' ' + xhr.statusText + "\n\n";
text += xhr.getAllResponseHeaders();
container.html(text);
};
var displayResponse = function(xhr, method, url, result_container) {
displayFinalUrl(xhr, method, url, $('.url', result_container));
displayResponseData(xhr, $('.response', result_container));
displayResponseHeaders(xhr, $('.headers', result_container));
result_container.show();
};
$('.pane.sandbox form').submit(function() {
var url = $(this).attr('action'),
method = $(this).attr('method'),
self = this,
params = {
_format: 'json' // default format if not overriden in the form
},
headers = {},
content = $(this).find('textarea.content').val();
result_container = $('.result', $(this).parent());
// retrieve all the parameters to send
$('.parameters .tuple', $(this)).each(function() {
var key, value;
key = $('.key', $(this)).val();
value = $('.value', $(this)).val();
if (value) {
params[key] = value;
}
});
// retrieve the additional headers to send
$('.headers .tuple', $(this)).each(function() {
var key, value;
key = $('.key', $(this)).val();
value = $('.value', $(this)).val();
if (value) {
headers[key] = value;
}
});
// fix parameters in URL
for (var key in $.extend({}, params)) {
if (url.indexOf('{' + key + '}') !== -1) {
url = url.replace('{' + key + '}', params[key]);
delete params[key];
}
}; };
// disable all the fiels and buttons var renderRawBody = function ($container) {
$('input, button', $(this)).attr('disabled', 'disabled'); var rawData, $btn;
// and trigger the API call rawData = $container.data('raw-response');
$.ajax({ $btn = $container.parents('.pane').find('.to-raw');
url: '{{ sandboxTarget }}' + url,
type: method,
data: content.length ? content : params,
headers: headers,
complete: function(xhr) {
displayResponse(xhr, method, url, result_container);
// and enable them back $container.addClass('prettyprinted');
$('input:not(.content-type), button', $(self)).removeAttr('disabled'); $container.html(rawData);
}
$btn.removeClass('to-raw');
$btn.addClass('to-prettify');
toggleButtonText($btn);
};
var renderPrettifiedBody = function ($container) {
var rawData, $btn;
rawData = $container.data('raw-response');
$btn = $container.parents('.pane').find('.to-prettify');
$container.removeClass('prettyprinted');
$container.html(prettifyResponse(rawData));
prettyPrint && prettyPrint();
$btn.removeClass('to-prettify');
$btn.addClass('to-raw');
toggleButtonText($btn);
};
$('.tabs li').click(function() {
var contentGroup = $(this).parents('.content');
$('.pane.selected', contentGroup).removeClass('selected');
$('.pane.' + $(this).data('pane'), contentGroup).addClass('selected');
$('li', $(this).parent()).removeClass('selected');
$(this).addClass('selected');
}); });
return false; var prettifyResponse = function(text) {
}); try {
var data = typeof text === 'string' ? JSON.parse(text) : text;
$('.pane.sandbox').delegate('.to-raw', 'click', function(e) { text = JSON.stringify(data, undefined, ' ');
renderRawBody($(this).parents('.pane').find('.response')); } catch (err) {
e.preventDefault();
});
$('.pane.sandbox').delegate('.to-prettify', 'click', function(e) {
renderPrettifiedBody($(this).parents('.pane').find('.response'));
e.preventDefault();
});
$('.pane.sandbox').delegate('.to-expand, .to-shrink', 'click', function(e) {
var $headers = $(this).parents('.result').find('.headers');
var $label = $(this).parents('.result').find('a.to-expand');
if ($headers.hasClass('to-expand')) {
$headers.removeClass('to-expand');
$headers.addClass('to-shrink');
$label.text('Shrink');
} else {
$headers.removeClass('to-shrink');
$headers.addClass('to-expand');
$label.text('Expand');
}
e.preventDefault();
});
$('.pane.sandbox').on('click', '.add', function() {
var html = $(this).parents('.pane').find('.tuple_template').html();
$(this).before(html);
return false;
});
$('.pane.sandbox').on('click', '.remove', function() {
$(this).parent().remove();
});
$('.pane.sandbox').on('click', '.set-content-type', function(e) {
var html;
var $element;
var $headers = $(this).parents('form').find('.headers');
var content_type = $(this).prev('input.value').val();
e.preventDefault();
if (content_type.length === 0) {
return;
}
$headers.find('input.key').each(function() {
if ($.trim($(this).val().toLowerCase()) === 'content-type') {
$element = $(this).parents('p');
return false;
} }
// HTML encode the result
return $('<div>').text(text).html();
};
var displayFinalUrl = function(xhr, method, url, container) {
container.html(method + ' ' + url);
};
var displayResponseData = function(xhr, container) {
var data = xhr.responseText;
container.data('raw-response', data);
if ('<' === data[0]) {
renderRawBody(container);
} else {
renderPrettifiedBody(container);
}
container.parents('.pane').find('.to-prettify').text('Raw');
container.parents('.pane').find('.to-raw').text('Raw');
};
var displayResponseHeaders = function(xhr, container) {
var text = xhr.status + ' ' + xhr.statusText + "\n\n";
text += xhr.getAllResponseHeaders();
container.html(text);
};
var displayResponse = function(xhr, method, url, result_container) {
displayFinalUrl(xhr, method, url, $('.url', result_container));
displayResponseData(xhr, $('.response', result_container));
displayResponseHeaders(xhr, $('.headers', result_container));
result_container.show();
};
$('.pane.sandbox form').submit(function() {
var url = $(this).attr('action'),
method = $(this).attr('method'),
self = this,
params = {
_format: 'json' // default format if not overriden in the form
},
headers = {},
content = $(this).find('textarea.content').val();
result_container = $('.result', $(this).parent());
// retrieve all the parameters to send
$('.parameters .tuple', $(this)).each(function() {
var key, value;
key = $('.key', $(this)).val();
value = $('.value', $(this)).val();
if (value) {
params[key] = value;
}
});
// retrieve the additional headers to send
$('.headers .tuple', $(this)).each(function() {
var key, value;
key = $('.key', $(this)).val();
value = $('.value', $(this)).val();
if (value) {
headers[key] = value;
}
});
// fix parameters in URL
for (var key in $.extend({}, params)) {
if (url.indexOf('{' + key + '}') !== -1) {
url = url.replace('{' + key + '}', params[key]);
delete params[key];
}
};
// disable all the fiels and buttons
$('input, button', $(this)).attr('disabled', 'disabled');
// and trigger the API call
$.ajax({
url: '{{ endpoint }}' + url,
type: method,
data: content.length ? content : params,
headers: headers,
complete: function(xhr) {
displayResponse(xhr, method, url, result_container);
// and enable them back
$('input:not(.content-type), button', $(self)).removeAttr('disabled');
}
});
return false;
}); });
if (typeof $element === 'undefined') { $('.pane.sandbox').delegate('.to-raw', 'click', function(e) {
html = $(this).parents('.pane').find('.tuple_template').html(); renderRawBody($(this).parents('.pane').find('.response'));
$element = $headers.find('legend').after(html).next('p'); e.preventDefault();
} });
$element.find('input.key').val('Content-Type'); $('.pane.sandbox').delegate('.to-prettify', 'click', function(e) {
$element.find('input.value').val(content_type); renderPrettifiedBody($(this).parents('.pane').find('.response'));
}); e.preventDefault();
});
$('.pane.sandbox').delegate('.to-expand, .to-shrink', 'click', function(e) {
var $headers = $(this).parents('.result').find('.headers');
var $label = $(this).parents('.result').find('a.to-expand');
if ($headers.hasClass('to-expand')) {
$headers.removeClass('to-expand');
$headers.addClass('to-shrink');
$label.text('Shrink');
} else {
$headers.removeClass('to-shrink');
$headers.addClass('to-expand');
$label.text('Expand');
}
e.preventDefault();
});
$('.pane.sandbox').on('click', '.add', function() {
var html = $(this).parents('.pane').find('.tuple_template').html();
$(this).before(html);
return false;
});
$('.pane.sandbox').on('click', '.remove', function() {
$(this).parent().remove();
});
$('.pane.sandbox').on('click', '.set-content-type', function(e) {
var html;
var $element;
var $headers = $(this).parents('form').find('.headers');
var content_type = $(this).prev('input.value').val();
e.preventDefault();
if (content_type.length === 0) {
return;
}
$headers.find('input.key').each(function() {
if ($.trim($(this).val().toLowerCase()) === 'content-type') {
$element = $(this).parents('p');
return false;
}
});
if (typeof $element === 'undefined') {
html = $(this).parents('.pane').find('.tuple_template').html();
$element = $headers.find('legend').after(html).next('p');
}
$element.find('input.key').val('Content-Type');
$element.find('input.value').val(content_type);
});
{% endif %}
</script> </script>
</body> </body>
</html> </html>

View File

@ -1,186 +1,190 @@
<li class="{{ data.method|lower }} operation"> <li class="{{ data.method|lower }} operation">
<div class="heading toggler"> <div class="heading toggler">
<h3> <h3>
<span class="http_method"> <span class="http_method">
<a>{{ data.method|upper }}</a> <a>{{ data.method|upper }}</a>
</span> </span>
<span class="path"> <span class="path">
{{ data.uri }} {{ data.uri }}
</span> </span>
</h3> </h3>
<ul class="options"> <ul class="options">
{% if data.description is defined %} {% if data.description is defined %}
<li>{{ data.description }}</li> <li>{{ data.description }}</li>
{% endif %} {% endif %}
</ul> </ul>
</div> </div>
<div class="content" style="display: {% if displayContent is defined and displayContent == true %}display{% else %}none{% endif %};"> <div class="content" style="display: {% if displayContent is defined and displayContent == true %}display{% else %}none{% endif %};">
<ul class="tabs"> <ul class="tabs">
<li class="selected" data-pane="content">Documentation</li> <li class="selected" data-pane="content">Documentation</li>
<li data-pane="sandbox">Sandbox</li> {% if enableSandbox %}
</ul> <li data-pane="sandbox">Sandbox</li>
{% endif %}
</ul>
<div class="panes"> <div class="panes">
<div class="pane content selected"> <div class="pane content selected">
{% if data.documentation is defined and data.documentation is not empty %} {% if data.documentation is defined and data.documentation is not empty %}
<h4>Documentation</h4> <h4>Documentation</h4>
<div>{{ data.documentation|markdown }}</div> <div>{{ data.documentation|markdown }}</div>
{% endif %} {% endif %}
{% if data.requirements is defined and data.requirements is not empty %} {% if data.requirements is defined and data.requirements is not empty %}
<h4>Requirements</h4> <h4>Requirements</h4>
<table class="fullwidth"> <table class="fullwidth">
<thead> <thead>
<tr> <tr>
<th>Name</th> <th>Name</th>
<th>Requirement</th> <th>Requirement</th>
<th>Type</th> <th>Type</th>
<th>Description</th> <th>Description</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% for name, infos in data.requirements %} {% for name, infos in data.requirements %}
<tr>
<td>{{ name }}</td>
<td>{{ infos.requirement }}</td>
<td>{{ infos.type }}</td>
<td>{{ infos.description }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endif %}
{% if data.filters is defined and data.filters is not empty %}
<h4>Filters</h4>
<table class="fullwidth">
<thead>
<tr>
<th>Name</th>
<th>Information</th>
</tr>
</thead>
<tbody>
{% for name, infos in data.filters %}
<tr> <tr>
<td>{{ name }}</td> <td>{{ name }}</td>
<td>{{ infos.requirement }}</td> <td>
<td>{{ infos.type }}</td> <table>
<td>{{ infos.description }}</td> {% for key, value in infos %}
<tr>
<td>{{ key|title }}</td>
<td>{{ value|json_encode|trim('"') }}</td>
</tr>
{% endfor %}
</table>
</td>
</tr> </tr>
{% endfor %} {% endfor %}
</tbody> </tbody>
</table> </table>
{% endif %} {% endif %}
{% if data.filters is defined and data.filters is not empty %} {% if data.parameters is defined and data.parameters is not empty %}
<h4>Filters</h4> <h4>Parameters</h4>
<table class="fullwidth"> <table class='fullwidth'>
<thead> <thead>
<tr>
<th>Name</th>
<th>Information</th>
</tr>
</thead>
<tbody>
{% for name, infos in data.filters %}
<tr>
<td>{{ name }}</td>
<td>
<table>
{% for key, value in infos %}
<tr>
<td>{{ key|title }}</td>
<td>{{ value|json_encode|trim('"') }}</td>
</tr>
{% endfor %}
</table>
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endif %}
{% if data.parameters is defined and data.parameters is not empty %}
<h4>Parameters</h4>
<table class='fullwidth'>
<thead>
<tr>
<th>Parameter</th>
<th>Type</th>
<th>Required?</th>
<th>Description</th>
</tr>
</thead>
<tbody>
{% for name, infos in data.parameters %}
<tr> <tr>
<td>{{ name }}</td> <th>Parameter</th>
<td>{{ infos.dataType }}</td> <th>Type</th>
<td>{{ infos.required ? 'true' : 'false' }}</td> <th>Required?</th>
<td>{{ infos.description }}</td> <th>Description</th>
</tr> </tr>
{% endfor %} </thead>
</tbody> <tbody>
</table>
{% endif %}
</div>
<div class="pane sandbox">
<form method="{{ data.method|upper }}" action="{{ data.uri }}">
<fieldset class="parameters">
<legend>Parameters</legend>
{% if data.parameters is defined %}
{% for name, infos in data.parameters %} {% for name, infos in data.parameters %}
<p class="tuple"> <tr>
<input type="text" class="key" value="{{ name }}" placeholder="Key" /> <td>{{ name }}</td>
<span>=</span> <td>{{ infos.dataType }}</td>
<input type="text" class="value" placeholder="{% if infos.dataType %}[{{ infos.dataType }}] {% endif %}{% if infos.description %}{{ infos.description }}{% else %}Value{% endif %}" /> <span class="remove">-</span> <td>{{ infos.required ? 'true' : 'false' }}</td>
</p> <td>{{ infos.description }}</td>
</tr>
{% endfor %} {% endfor %}
{% endif %} </tbody>
{% if data.filters is defined %} </table>
{% for name, infos in data.filters %} {% endif %}
<p class="tuple">
<input type="text" class="key" value="{{ name }}" placeholder="Key" />
<span>=</span>
<input type="text" class="value" placeholder="{% if infos.description is defined %}{{ infos.description }}{% else %}Value{% endif %}" /> <span class="remove">-</span>
</p>
{% endfor %}
{% endif %}
<button class="add">New parameter</button>
</fieldset>
<fieldset class="headers">
<legend>Headers</legend>
<p class="tuple">
<input type="text" class="key" placeholder="Key" />
<span>=</span>
<input type="text" class="value" placeholder="Value" /> <span class="remove">-</span>
</p>
<button class="add">New header</button>
</fieldset>
<fieldset class="request-content">
<legend>Content</legend>
<textarea class="content" placeholder="Content set here will override the parameters that do not match the url"></textarea>
<p class="tuple">
<input type="text" class="key content-type" value="Content-Type" disabled="disabled" />
<span>=</span>
<input type="text" class="value" placeholder="Value" />
<button class="set-content-type">Set header</button> <small>Replaces header if set</small>
</p>
</fieldset>
<div class="buttons">
<input type="submit" value="Try!" />
</div>
</form>
<script type="text/x-tmpl" class="tuple_template">
<p class="tuple">
<input type="text" class="key" placeholder="Key" />
<span>=</span>
<input type="text" class="value" placeholder="Value" /> <span class="remove">-</span>
</p>
</script>
<div class="result">
<h4>Request URL</h4>
<pre class="url"></pre>
<h4>Response Headers&nbsp;<small>[<a href="" class="to-expand">Expand</a>]</small></h4>
<pre class="headers to-expand"></pre>
<h4>Response Body&nbsp;<small>[<a href="" class="to-raw">Raw</a>]</small></h4>
<pre class="response prettyprint"></pre>
</div> </div>
{% if enableSandbox %}
<div class="pane sandbox">
<form method="{{ data.method|upper }}" action="{{ data.uri }}">
<fieldset class="parameters">
<legend>Parameters</legend>
{% if data.parameters is defined %}
{% for name, infos in data.parameters %}
<p class="tuple">
<input type="text" class="key" value="{{ name }}" placeholder="Key" />
<span>=</span>
<input type="text" class="value" placeholder="{% if infos.dataType %}[{{ infos.dataType }}] {% endif %}{% if infos.description %}{{ infos.description }}{% else %}Value{% endif %}" /> <span class="remove">-</span>
</p>
{% endfor %}
{% endif %}
{% if data.filters is defined %}
{% for name, infos in data.filters %}
<p class="tuple">
<input type="text" class="key" value="{{ name }}" placeholder="Key" />
<span>=</span>
<input type="text" class="value" placeholder="{% if infos.description is defined %}{{ infos.description }}{% else %}Value{% endif %}" /> <span class="remove">-</span>
</p>
{% endfor %}
{% endif %}
<button class="add">New parameter</button>
</fieldset>
<fieldset class="headers">
<legend>Headers</legend>
<p class="tuple">
<input type="text" class="key" placeholder="Key" />
<span>=</span>
<input type="text" class="value" placeholder="Value" /> <span class="remove">-</span>
</p>
<button class="add">New header</button>
</fieldset>
<fieldset class="request-content">
<legend>Content</legend>
<textarea class="content" placeholder="Content set here will override the parameters that do not match the url"></textarea>
<p class="tuple">
<input type="text" class="key content-type" value="Content-Type" disabled="disabled" />
<span>=</span>
<input type="text" class="value" placeholder="Value" />
<button class="set-content-type">Set header</button> <small>Replaces header if set</small>
</p>
</fieldset>
<div class="buttons">
<input type="submit" value="Try!" />
</div>
</form>
<script type="text/x-tmpl" class="tuple_template">
<p class="tuple">
<input type="text" class="key" placeholder="Key" />
<span>=</span>
<input type="text" class="value" placeholder="Value" /> <span class="remove">-</span>
</p>
</script>
<div class="result">
<h4>Request URL</h4>
<pre class="url"></pre>
<h4>Response Headers&nbsp;<small>[<a href="" class="to-expand">Expand</a>]</small></h4>
<pre class="headers to-expand"></pre>
<h4>Response Body&nbsp;<small>[<a href="" class="to-raw">Raw</a>]</small></h4>
<pre class="response prettyprint"></pre>
</div>
</div>
{% endif %}
</div> </div>
</div>
</div> </div>
</li> </li>