django-rest-framework/topics/api-clients/index.html
2017-02-25 19:59:44 +00:00

737 lines
31 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta charset="utf-8">
<title>API Clients - Django REST framework</title>
<link href="../../img/favicon.ico" rel="icon" type="image/x-icon">
<link rel="canonical" href="http://www.django-rest-framework.org/topics/api-clients/" />
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="Django, API, REST, API Clients">
<meta name="author" content="Tom Christie">
<!-- Le styles -->
<link href="../../css/prettify.css" rel="stylesheet">
<link href="../../css/bootstrap.css" rel="stylesheet">
<link href="../../css/bootstrap-responsive.css" rel="stylesheet">
<link href="../../css/default.css" rel="stylesheet">
<!-- Le HTML5 shim, for IE6-8 support of HTML5 elements -->
<!--[if lt IE 9]>
<script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<script type="text/javascript">
var _gaq = _gaq || [];
_gaq.push(['_setAccount', 'UA-18852272-2']);
_gaq.push(['_trackPageview']);
(function() {
var ga = document.createElement('script');
ga.type = 'text/javascript';
ga.async = true;
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
var s = document.getElementsByTagName('script')[0];
s.parentNode.insertBefore(ga, s);
})();
</script>
<style>
#sidebarInclude img {
margin-bottom: 10px;
}
#sidebarInclude a.promo {
color: black;
}
@media (max-width: 767px) {
div.promo {
display: none;
}
}
</style>
</head>
<body onload="prettyPrint()" class="-page">
<div class="wrapper">
<div class="navbar navbar-inverse navbar-fixed-top">
<div class="navbar-inner">
<div class="container-fluid">
<a class="repo-link btn btn-primary btn-small" href="https://github.com/tomchristie/django-rest-framework/tree/master">GitHub</a>
<a class="repo-link btn btn-inverse btn-small " rel="prev" href="../internationalization/">
Next <i class="icon-arrow-right icon-white"></i>
</a>
<a class="repo-link btn btn-inverse btn-small " rel="next" href="../documenting-your-api/">
<i class="icon-arrow-left icon-white"></i> Previous
</a>
<a id="search_modal_show" class="repo-link btn btn-inverse btn-small" href="#mkdocs_search_modal" data-toggle="modal" data-target="#mkdocs_search_modal"><i class="icon-search icon-white"></i> Search</a>
<a class="btn btn-navbar" data-toggle="collapse" data-target=".nav-collapse">
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</a>
<a class="brand" href="http://www.django-rest-framework.org">Django REST framework</a>
<div class="nav-collapse collapse">
<!-- Main navigation -->
<ul class="nav navbar-nav">
<li >
<a href="../..">Home</a>
</li>
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown">Tutorial <b class="caret"></b></a>
<ul class="dropdown-menu">
<li >
<a href="../../tutorial/quickstart/">Quickstart</a>
</li>
<li >
<a href="../../tutorial/1-serialization/">1 - Serialization</a>
</li>
<li >
<a href="../../tutorial/2-requests-and-responses/">2 - Requests and responses</a>
</li>
<li >
<a href="../../tutorial/3-class-based-views/">3 - Class based views</a>
</li>
<li >
<a href="../../tutorial/4-authentication-and-permissions/">4 - Authentication and permissions</a>
</li>
<li >
<a href="../../tutorial/5-relationships-and-hyperlinked-apis/">5 - Relationships and hyperlinked APIs</a>
</li>
<li >
<a href="../../tutorial/6-viewsets-and-routers/">6 - Viewsets and routers</a>
</li>
<li >
<a href="../../tutorial/7-schemas-and-client-libraries/">7 - Schemas and client libraries</a>
</li>
</ul>
</li>
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown">API Guide <b class="caret"></b></a>
<ul class="dropdown-menu">
<li >
<a href="../../api-guide/requests/">Requests</a>
</li>
<li >
<a href="../../api-guide/responses/">Responses</a>
</li>
<li >
<a href="../../api-guide/views/">Views</a>
</li>
<li >
<a href="../../api-guide/generic-views/">Generic views</a>
</li>
<li >
<a href="../../api-guide/viewsets/">Viewsets</a>
</li>
<li >
<a href="../../api-guide/routers/">Routers</a>
</li>
<li >
<a href="../../api-guide/parsers/">Parsers</a>
</li>
<li >
<a href="../../api-guide/renderers/">Renderers</a>
</li>
<li >
<a href="../../api-guide/serializers/">Serializers</a>
</li>
<li >
<a href="../../api-guide/fields/">Serializer fields</a>
</li>
<li >
<a href="../../api-guide/relations/">Serializer relations</a>
</li>
<li >
<a href="../../api-guide/validators/">Validators</a>
</li>
<li >
<a href="../../api-guide/authentication/">Authentication</a>
</li>
<li >
<a href="../../api-guide/permissions/">Permissions</a>
</li>
<li >
<a href="../../api-guide/throttling/">Throttling</a>
</li>
<li >
<a href="../../api-guide/filtering/">Filtering</a>
</li>
<li >
<a href="../../api-guide/pagination/">Pagination</a>
</li>
<li >
<a href="../../api-guide/versioning/">Versioning</a>
</li>
<li >
<a href="../../api-guide/content-negotiation/">Content negotiation</a>
</li>
<li >
<a href="../../api-guide/metadata/">Metadata</a>
</li>
<li >
<a href="../../api-guide/schemas/">Schemas</a>
</li>
<li >
<a href="../../api-guide/format-suffixes/">Format suffixes</a>
</li>
<li >
<a href="../../api-guide/reverse/">Returning URLs</a>
</li>
<li >
<a href="../../api-guide/exceptions/">Exceptions</a>
</li>
<li >
<a href="../../api-guide/status-codes/">Status codes</a>
</li>
<li >
<a href="../../api-guide/testing/">Testing</a>
</li>
<li >
<a href="../../api-guide/settings/">Settings</a>
</li>
</ul>
</li>
<li class="dropdown active">
<a href="#" class="dropdown-toggle" data-toggle="dropdown">Topics <b class="caret"></b></a>
<ul class="dropdown-menu">
<li >
<a href="../documenting-your-api/">Documenting your API</a>
</li>
<li class="active" >
<a href="./">API Clients</a>
</li>
<li >
<a href="../internationalization/">Internationalization</a>
</li>
<li >
<a href="../ajax-csrf-cors/">AJAX, CSRF & CORS</a>
</li>
<li >
<a href="../html-and-forms/">HTML & Forms</a>
</li>
<li >
<a href="../browser-enhancements/">Browser Enhancements</a>
</li>
<li >
<a href="../browsable-api/">The Browsable API</a>
</li>
<li >
<a href="../rest-hypermedia-hateoas/">REST, Hypermedia & HATEOAS</a>
</li>
<li >
<a href="../third-party-packages/">Third Party Packages</a>
</li>
<li >
<a href="../tutorials-and-resources/">Tutorials and Resources</a>
</li>
<li >
<a href="../contributing/">Contributing to REST framework</a>
</li>
<li >
<a href="../project-management/">Project management</a>
</li>
<li >
<a href="../3.0-announcement/">3.0 Announcement</a>
</li>
<li >
<a href="../3.1-announcement/">3.1 Announcement</a>
</li>
<li >
<a href="../3.2-announcement/">3.2 Announcement</a>
</li>
<li >
<a href="../3.3-announcement/">3.3 Announcement</a>
</li>
<li >
<a href="../3.4-announcement/">3.4 Announcement</a>
</li>
<li >
<a href="../3.5-announcement/">3.5 Announcement</a>
</li>
<li >
<a href="../kickstarter-announcement/">Kickstarter Announcement</a>
</li>
<li >
<a href="../mozilla-grant/">Mozilla Grant</a>
</li>
<li >
<a href="../funding/">Funding</a>
</li>
<li >
<a href="../release-notes/">Release Notes</a>
</li>
<li >
<a href="../jobs/">Jobs</a>
</li>
</ul>
</li>
</ul>
</div>
<!--/.nav-collapse -->
</div>
</div>
</div>
<div class="body-content">
<div class="container-fluid">
<!-- Search Modal -->
<div id="mkdocs_search_modal" class="modal hide fade" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
<h3 id="myModalLabel">Documentation search</h3>
</div>
<div class="modal-body">
<form role="form" autocomplete="off">
<div class="form-group">
<input type="text" name="q" class="form-control" placeholder="Search..." id="mkdocs-search-query">
</div>
</form>
<div id="mkdocs-search-results"></div>
</div>
<div class="modal-footer">
<button class="btn" data-dismiss="modal" aria-hidden="true">Close</button>
</div>
</div>
<div class="row-fluid">
<div class="span3">
<div id="table-of-contents">
<ul class="nav nav-list side-nav well sidebar-nav-fixed">
<li class="main">
<a href="#api-clients">API Clients</a>
</li>
<li>
<a href="#client-side-core-api">Client-side Core API</a>
</li>
<li class="main">
<a href="#command-line-client">Command line client</a>
</li>
<li>
<a href="#getting-started">Getting started</a>
</li>
<li>
<a href="#authentication-headers">Authentication &amp; headers</a>
</li>
<li>
<a href="#codecs">Codecs</a>
</li>
<li>
<a href="#utilities">Utilities</a>
</li>
<li>
<a href="#other-commands">Other commands</a>
</li>
<li class="main">
<a href="#python-client-library">Python client library</a>
</li>
<li>
<a href="#getting-started_1">Getting started</a>
</li>
<li>
<a href="#codecs_1">Codecs</a>
</li>
<li>
<a href="#transports">Transports</a>
</li>
<div class="promo">
<hr/>
<div id="sidebarInclude">
</div>
</ul>
</div>
</div>
<div id="main-content" class="span9">
<h1 id="api-clients"><a class="toclink" href="#api-clients">API Clients</a></h1>
<p>An API client handles the underlying details of how network requests are made
and how responses are decoded. They present the developer with an application
interface to work against, rather than working directly with the network interface.</p>
<p>The API clients documented here are not restricted to APIs built with Django REST framework.
They can be used with any API that exposes a supported schema format.</p>
<p>For example, <a href="https://devcenter.heroku.com/categories/platform-api">the Heroku platform API</a> exposes a schema in the JSON
Hyperschema format. As a result, the Core API command line client and Python
client library can be <a href="http://www.coreapi.org/tools-and-resources/example-services/#heroku-json-hyper-schema">used to interact with the Heroku API</a>.</p>
<h2 id="client-side-core-api"><a class="toclink" href="#client-side-core-api">Client-side Core API</a></h2>
<p><a href="http://www.coreapi.org/">Core API</a> is a document specification that can be used to describe APIs. It can
be used either server-side, as is done with REST framework's <a href="../../api-guide/schemas/">schema generation</a>,
or used client-side, as described here.</p>
<p>When used client-side, Core API allows for <em>dynamically driven client libraries</em>
that can interact with any API that exposes a supported schema or hypermedia
format.</p>
<p>Using a dynamically driven client has a number of advantages over interacting
with an API by building HTTP requests directly.</p>
<h4 id="more-meaningful-interaction"><a class="toclink" href="#more-meaningful-interaction">More meaningful interaction</a></h4>
<p>API interactions are presented in a more meaningful way. You're working at
the application interface layer, rather than the network interface layer.</p>
<h4 id="resilience-evolvability"><a class="toclink" href="#resilience-evolvability">Resilience &amp; evolvability</a></h4>
<p>The client determines what endpoints are available, what parameters exist
against each particular endpoint, and how HTTP requests are formed.</p>
<p>This also allows for a degree of API evolvability. URLs can be modified
without breaking existing clients, or more efficient encodings can be used
on-the-wire, with clients transparently upgrading.</p>
<h4 id="self-descriptive-apis"><a class="toclink" href="#self-descriptive-apis">Self-descriptive APIs</a></h4>
<p>A dynamically driven client is able to present documentation on the API to the
end user. This documentation allows the user to discover the available endpoints
and parameters, and better understand the API they are working with.</p>
<p>Because this documentation is driven by the API schema it will always be fully
up to date with the most recently deployed version of the service.</p>
<hr />
<h1 id="command-line-client"><a class="toclink" href="#command-line-client">Command line client</a></h1>
<p>The command line client allows you to inspect and interact with any API that
exposes a supported schema format.</p>
<h2 id="getting-started"><a class="toclink" href="#getting-started">Getting started</a></h2>
<p>To install the Core API command line client, use <code>pip</code>.</p>
<p>Note that the command-line client is a separate package to the
python client library. Make sure to install <code>coreapi-cli</code>.</p>
<pre><code>$ pip install coreapi-cli
</code></pre>
<p>To start inspecting and interacting with an API the schema must first be loaded
from the network.</p>
<pre><code>$ coreapi get http://api.example.org/
&lt;Pastebin API "http://127.0.0.1:8000/"&gt;
snippets: {
create(code, [title], [linenos], [language], [style])
destroy(pk)
highlight(pk)
list([page])
partial_update(pk, [title], [code], [linenos], [language], [style])
retrieve(pk)
update(pk, code, [title], [linenos], [language], [style])
}
users: {
list([page])
retrieve(pk)
}
</code></pre>
<p>This will then load the schema, displaying the resulting <code>Document</code>. This
<code>Document</code> includes all the available interactions that may be made against the API.</p>
<p>To interact with the API, use the <code>action</code> command. This command requires a list
of keys that are used to index into the link.</p>
<pre><code>$ coreapi action users list
[
{
"url": "http://127.0.0.1:8000/users/2/",
"id": 2,
"username": "aziz",
"snippets": []
},
...
]
</code></pre>
<p>To inspect the underlying HTTP request and response, use the <code>--debug</code> flag.</p>
<pre><code>$ coreapi action users list --debug
&gt; GET /users/ HTTP/1.1
&gt; Accept: application/vnd.coreapi+json, */*
&gt; Authorization: Basic bWF4Om1heA==
&gt; Host: 127.0.0.1
&gt; User-Agent: coreapi
&lt; 200 OK
&lt; Allow: GET, HEAD, OPTIONS
&lt; Content-Type: application/json
&lt; Date: Thu, 30 Jun 2016 10:51:46 GMT
&lt; Server: WSGIServer/0.1 Python/2.7.10
&lt; Vary: Accept, Cookie
&lt;
&lt; [{"url":"http://127.0.0.1/users/2/","id":2,"username":"aziz","snippets":[]},{"url":"http://127.0.0.1/users/3/","id":3,"username":"amy","snippets":["http://127.0.0.1/snippets/3/"]},{"url":"http://127.0.0.1/users/4/","id":4,"username":"max","snippets":["http://127.0.0.1/snippets/4/","http://127.0.0.1/snippets/5/","http://127.0.0.1/snippets/6/","http://127.0.0.1/snippets/7/"]},{"url":"http://127.0.0.1/users/5/","id":5,"username":"jose","snippets":[]},{"url":"http://127.0.0.1/users/6/","id":6,"username":"admin","snippets":["http://127.0.0.1/snippets/1/","http://127.0.0.1/snippets/2/"]}]
[
...
]
</code></pre>
<p>Some actions may include optional or required parameters.</p>
<pre><code>$ coreapi action users create --param username=example
</code></pre>
<p>When using <code>--param</code>, the type of the input will be determined automatically.</p>
<p>If you want to be more explicit about the parameter type then use <code>--data</code> for
any null, numeric, boolean, list, or object inputs, and use <code>--string</code> for string inputs.</p>
<pre><code>$ coreapi action users edit --string username=tomchristie --data is_admin=true
</code></pre>
<h2 id="authentication-headers"><a class="toclink" href="#authentication-headers">Authentication &amp; headers</a></h2>
<p>The <code>credentials</code> command is used to manage the request <code>Authentication:</code> header.
Any credentials added are always linked to a particular domain, so as to ensure
that credentials are not leaked across differing APIs.</p>
<p>The format for adding a new credential is:</p>
<pre><code>$ coreapi credentials add &lt;domain&gt; &lt;credentials string&gt;
</code></pre>
<p>For instance:</p>
<pre><code>$ coreapi credentials add api.example.org "Token 9944b09199c62bcf9418ad846dd0e4bbdfc6ee4b"
</code></pre>
<p>The optional <code>--auth</code> flag also allows you to add specific types of authentication,
handling the encoding for you. Currently only <code>"basic"</code> is supported as an option here.
For example:</p>
<pre><code>$ coreapi credentials add api.example.org tomchristie:foobar --auth basic
</code></pre>
<p>You can also add specific request headers, using the <code>headers</code> command:</p>
<pre><code>$ coreapi headers add api.example.org x-api-version 2
</code></pre>
<p>For more information and a listing of the available subcommands use <code>coreapi
credentials --help</code> or <code>coreapi headers --help</code>.</p>
<h2 id="codecs"><a class="toclink" href="#codecs">Codecs</a></h2>
<p>By default the command line client only includes support for reading Core JSON
schemas, however it includes a plugin system for installing additional codecs.</p>
<pre><code>$ pip install openapi-codec jsonhyperschema-codec hal-codec
$ coreapi codecs show
Codecs
corejson application/vnd.coreapi+json encoding, decoding
hal application/hal+json encoding, decoding
openapi application/openapi+json encoding, decoding
jsonhyperschema application/schema+json decoding
json application/json data
text text/* data
</code></pre>
<h2 id="utilities"><a class="toclink" href="#utilities">Utilities</a></h2>
<p>The command line client includes functionality for bookmarking API URLs
under a memorable name. For example, you can add a bookmark for the
existing API, like so...</p>
<pre><code>$ coreapi bookmarks add accountmanagement
</code></pre>
<p>There is also functionality for navigating forward or backward through the
history of which API URLs have been accessed.</p>
<pre><code>$ coreapi history show
$ coreapi history back
</code></pre>
<p>For more information and a listing of the available subcommands use
<code>coreapi bookmarks --help</code> or <code>coreapi history --help</code>.</p>
<h2 id="other-commands"><a class="toclink" href="#other-commands">Other commands</a></h2>
<p>To display the current <code>Document</code>:</p>
<pre><code>$ coreapi show
</code></pre>
<p>To reload the current <code>Document</code> from the network:</p>
<pre><code>$ coreapi reload
</code></pre>
<p>To load a schema file from disk:</p>
<pre><code>$ coreapi load my-api-schema.json --format corejson
</code></pre>
<p>To dump the current document to console in a given format:</p>
<pre><code>$ coreapi dump --format openapi
</code></pre>
<p>To remove the current document, along with all currently saved history,
credentials, headers and bookmarks:</p>
<pre><code>$ coreapi clear
</code></pre>
<hr />
<h1 id="python-client-library"><a class="toclink" href="#python-client-library">Python client library</a></h1>
<p>The <code>coreapi</code> Python package allows you to programmatically interact with any
API that exposes a supported schema format.</p>
<h2 id="getting-started_1"><a class="toclink" href="#getting-started_1">Getting started</a></h2>
<p>You'll need to install the <code>coreapi</code> package using <code>pip</code> before you can get
started.</p>
<pre><code>$ pip install coreapi
</code></pre>
<p>In order to start working with an API, we first need a <code>Client</code> instance. The
client holds any configuration around which codecs and transports are supported
when interacting with an API, which allows you to provide for more advanced
kinds of behaviour.</p>
<pre><code>import coreapi
client = coreapi.Client()
</code></pre>
<p>Once we have a <code>Client</code> instance, we can fetch an API schema from the network.</p>
<pre><code>schema = client.get('https://api.example.org/')
</code></pre>
<p>The object returned from this call will be a <code>Document</code> instance, which is
the internal representation of the interface that we are interacting with.</p>
<p>Now that we have our schema <code>Document</code>, we can now start to interact with the API:</p>
<pre><code>users = client.action(schema, ['users', 'list'])
</code></pre>
<p>Some endpoints may include named parameters, which might be either optional or required:</p>
<pre><code>new_user = client.action(schema, ['users', 'create'], params={"username": "max"})
</code></pre>
<h2 id="codecs_1"><a class="toclink" href="#codecs_1">Codecs</a></h2>
<p>Codecs are responsible for encoding or decoding Documents.</p>
<p>The decoding process is used by a client to take a bytestring of an API schema
definition, and returning the Core API <code>Document</code> that represents that interface.</p>
<p>A codec should be associated with a particular media type, such as <code>'application/coreapi+json'</code>.</p>
<p>This media type is used by the server in the response <code>Content-Type</code> header,
in order to indicate what kind of data is being returned in the response.</p>
<h4 id="configuring-codecs"><a class="toclink" href="#configuring-codecs">Configuring codecs</a></h4>
<p>The codecs that are available can be configured when instantiating a client.
The keyword argument used here is <code>decoders</code>, because in the context of a
client the codecs are only for <em>decoding</em> responses.</p>
<p>In the following example we'll configure a client to only accept <code>Core JSON</code>
and <code>JSON</code> responses. This will allow us to receive and decode a Core JSON schema,
and subsequently to receive JSON responses made against the API.</p>
<pre><code>from coreapi import codecs, Client
decoders = [codecs.CoreJSONCodec(), codecs.JSONCodec()]
client = Client(decoders=decoders)
</code></pre>
<h4 id="loading-and-saving-schemas"><a class="toclink" href="#loading-and-saving-schemas">Loading and saving schemas</a></h4>
<p>You can use a codec directly, in order to load an existing schema definition,
and return the resulting <code>Document</code>.</p>
<pre><code>input_file = open('my-api-schema.json', 'rb')
schema_definition = input_file.read()
codec = codecs.CoreJSONCodec()
schema = codec.load(schema_definition)
</code></pre>
<p>You can also use a codec directly to generate a schema definition given a <code>Document</code> instance:</p>
<pre><code>schema_definition = codec.dump(schema)
output_file = open('my-api-schema.json', 'rb')
output_file.write(schema_definition)
</code></pre>
<h2 id="transports"><a class="toclink" href="#transports">Transports</a></h2>
<p>Transports are responsible for making network requests. The set of transports
that a client has installed determines which network protocols it is able to
support.</p>
<p>Currently the <code>coreapi</code> library only includes an HTTP/HTTPS transport, but
other protocols can also be supported.</p>
<h4 id="configuring-transports"><a class="toclink" href="#configuring-transports">Configuring transports</a></h4>
<p>The behaviour of the network layer can be customized by configuring the
transports that the client is instantiated with.</p>
<pre><code>import requests
from coreapi import transports, Client
credentials = {'api.example.org': 'Token 3bd44a009d16ff'}
transports = transports.HTTPTransport(credentials=credentials)
client = Client(transports=transports)
</code></pre>
<p>More complex customizations can also be achieved, for example modifying the
underlying <code>requests.Session</code> instance to <a href="http://docs.python-requests.org/en/master/user/advanced/#transport-adapters">attach transport adaptors</a>
that modify the outgoing requests.</p>
</div> <!--/span-->
</div> <!--/row-->
</div> <!--/.fluid-container-->
</div> <!--/.body content-->
<div id="push"></div>
</div> <!--/.wrapper -->
<footer class="span12">
<p>Documentation built with <a href="http://www.mkdocs.org/">MkDocs</a>.
</p>
</footer>
<!-- Le javascript
================================================== -->
<!-- Placed at the end of the document so the pages load faster -->
<script src="../../js/jquery-1.8.1-min.js"></script>
<script src="../../js/prettify-1.0.js"></script>
<script src="../../js/bootstrap-2.1.1-min.js"></script>
<script src="https://fund.django-rest-framework.org/sidebar_include.js"></script>
<script>var base_url = '../..';</script>
<script src="../../mkdocs/js/require.js"></script>
<script src="../../js/theme.js"></script>
<script>
var shiftWindow = function() {
scrollBy(0, -50)
};
if (location.hash) shiftWindow();
window.addEventListener("hashchange", shiftWindow);
$('.dropdown-menu').on('click touchstart', function(event) {
event.stopPropagation();
});
// Dynamically force sidenav/dropdown to no higher than browser window
$('.side-nav, .dropdown-menu').css('max-height', window.innerHeight - 130);
$(function() {
$(window).resize(function() {
$('.side-nav, .dropdown-menu').css('max-height', window.innerHeight - 130);
});
});
</script>
</body>
</html>