Repository: trinopoty/socket.io-server-java
Branch: master
Commit: 46a62d7c95b1
Files: 78
Total size: 357.4 KB
Directory structure:
gitextract_i9rurojh/
├── .codecov.yml
├── .gitignore
├── .travis.yml
├── LICENSE
├── README.md
├── codecov.yml
├── deploy-docs.sh
├── docs/
│ ├── .gitignore
│ ├── Makefile
│ ├── _static/
│ │ └── .keep
│ ├── _themes/
│ │ └── sphinx_rtd_theme/
│ │ ├── __init__.py
│ │ ├── breadcrumbs.html
│ │ ├── footer.html
│ │ ├── layout.html
│ │ ├── search.html
│ │ ├── searchbox.html
│ │ ├── static/
│ │ │ ├── css/
│ │ │ │ ├── badge_only.css
│ │ │ │ └── theme.css
│ │ │ ├── fonts/
│ │ │ │ └── FontAwesome.otf
│ │ │ └── js/
│ │ │ └── theme.js
│ │ ├── theme.conf
│ │ └── versions.html
│ ├── api.rst
│ ├── conf.py
│ ├── index.rst
│ ├── install.rst
│ ├── javadocs/
│ │ └── index.rst
│ ├── requirements.txt
│ └── using.rst
├── pom.xml
├── pom.xml.versionsBackup
├── socket.io-server/
│ ├── pom.xml
│ ├── pom.xml.versionsBackup
│ └── src/
│ ├── main/
│ │ └── java/
│ │ └── io/
│ │ └── socket/
│ │ └── socketio/
│ │ └── server/
│ │ ├── PacketUtils.java
│ │ ├── SocketIoAdapter.java
│ │ ├── SocketIoClient.java
│ │ ├── SocketIoMemoryAdapter.java
│ │ ├── SocketIoNamespace.java
│ │ ├── SocketIoNamespaceGroupImpl.java
│ │ ├── SocketIoNamespaceImpl.java
│ │ ├── SocketIoNamespaceProvider.java
│ │ ├── SocketIoServer.java
│ │ ├── SocketIoServerOptions.java
│ │ ├── SocketIoSocket.java
│ │ ├── parser/
│ │ │ ├── Binary.java
│ │ │ ├── DecodingException.java
│ │ │ ├── IOParser.java
│ │ │ ├── Packet.java
│ │ │ └── Parser.java
│ │ └── utils/
│ │ └── HasBinary.java
│ └── test/
│ └── java/
│ └── io/
│ └── socket/
│ └── socketio/
│ └── server/
│ ├── SocketIoAdapterImpl.java
│ ├── SocketIoAdapterTest.java
│ ├── SocketIoClientTest.java
│ ├── SocketIoMemoryAdapterTest.java
│ ├── SocketIoNamespaceImplTest.java
│ ├── SocketIoServerOptionsTest.java
│ ├── SocketIoServerTest.java
│ ├── SocketIoSocketTest.java
│ └── StubEngineIoWebSocket.java
└── socket.io-server-test/
├── pom.xml
├── pom.xml.versionsBackup
└── src/
└── test/
├── java/
│ └── io/
│ └── socket/
│ └── socketio/
│ └── server/
│ ├── JettyEngineIoWebSocketHandler.java
│ ├── ServerWrapper.java
│ ├── SocketIoTest.java
│ └── Utils.java
└── resources/
├── package.json
├── test_broadcast_to_all_clients.js
├── test_broadcast_to_all_clients_except_one.js
├── test_broadcast_to_multiple_rooms.js
├── test_broadcast_to_one_room.js
├── test_connect.js
├── test_connect_dynamic.js
├── test_message_to_client_binary_noack.js
├── test_message_to_client_nonbinary_ack.js
├── test_message_to_client_nonbinary_noack.js
├── test_message_to_server_binary_noack.js
├── test_message_to_server_nonbinary_ack.js
└── test_message_to_server_nonbinary_noack.js
================================================
FILE CONTENTS
================================================
================================================
FILE: .codecov.yml
================================================
codecov:
notify:
require_ci_to_pass: yes
coverage:
precision: 2
round: down
status:
project: yes
patch: yes
changes: no
parsers:
gcov:
branch_detection:
conditional: yes
loop: yes
method: no
macro: no
comment:
layout: "header, diff"
behavior: default
require_changes: no
================================================
FILE: .gitignore
================================================
*.class
# Package Files #
*.jar
*.war
*.ear
# Intellij project files
*.iml
*.ipr
*.iws
.idea/
.DS_Store
target/
node_modules/
================================================
FILE: .travis.yml
================================================
language: java
dist: xenial
sudo: false
branches:
except:
- gh-pages
env:
global:
- secure: x8A+yQ3K/iN8maKtml5H9arQQxsjc6AgX7IfuwU8AiCxkoZKWZit25OHfVB4N6cTo16+BBF3lTgRBS4SnDhYjGrLbzlfNCyv+RN4BXIGt2p+6Vq2qgkhWP2yOeGbsySibkEG4Fd0UKzOPwkDSy0nXx8h4b9nSfRMLdE1XL6QwmJMVVOpTJxUg5eL948XWcFdqF6TRCFUijNbUoBvnCKHTMh6q9fVTRMNwKeoT0dzXxbDlxV9F82wI1XwkRSYsuuc8zy5jWoxI8kfFaIq0P+tMikYRKkEDaWIX8cQ5Cz1wXOLMuOYHKKwmTtXTw0vvKGowTDj3JO2tnZ4/qWiHSNlJyPK834VHVxN1cCamwIQ9sr72vm5TGoG9rflRCmKemJgLPswAP1KmWTvX6t1UhLCngkscxcKbJxMMgdqCmYWG/rjX8wtUk2QaPcr7d0xswc8Ozqy8MlBmkkghjj1Z9fS5aT/wH+mf59E12DlKZbtimqqrs4a2Tm1QN0u4sLsO1se1VBnW8+abB+z6WrmuIn6H3xj3NULrSm9a6Dgn20ytkDZyyzy0GMRazXr4hzjusS0ifNJ2PWljOSxsuIU8qZAqEh+vsf75+ERJ2hJ654r6NcmiHBxkpmAMcUkhsRinG11wN1n37fAiLGVybglPfDCygJozr9dBIMUk+Q1aa4Y9K4=
addons:
apt:
update: true
matrix:
include:
- name: "Java 8 Tests"
jdk: openjdk8
env: TESTENV=tests
script: mvn test
- name: "GitHub Pages"
jdk: openjdk8
env: TESTENV=cover FYI="this also builds documentation for tags"
script: '[[ -n "$TRAVIS_TAG" ]] && ./deploy-docs.sh || true'
addons:
apt:
packages:
- python3
- python3-pip
- python-virtualenv
- make
after_success:
- '[[ "$TESTENV" == "tests" ]] && bash <(curl -s https://codecov.io/bash) -f engine.io-server-coverage/target/site/jacoco-aggregate/jacoco.xml'
cache:
directories:
- $HOME/.m2
================================================
FILE: LICENSE
================================================
Copyright 2022 Trinopoty Biswas
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
================================================
FILE: README.md
================================================
# Socket.IO Java
[](https://travis-ci.org/trinopoty/socket.io-server-java) [](https://codecov.io/gh/trinopoty/socket.io-server-java)
This is the Socket.IO Server Library for Java ported from the [JavaScript server](https://github.com/socketio/socket.io).
**NOTE** This library will follow the major version of the JS library starting with version 3.
See also: [Socket.IO-client Java](https://github.com/socketio/socket.io-client-java)
## Features
This library supports all of the features the JS server does, including events, options and upgrading transport.
## Documentation
Complete documentation can be found [here](https://trinopoty.github.io/socket.io-server-java/).
## Installation
The latest artifact is available on Maven Central.
### Maven
Add the following dependency to your `pom.xml`.
```xml
io.socket
socket.io-server
4.0.1
```
### Gradle
Add it as a gradle dependency in `build.gradle`.
```groovy
compile ('io.socket:socket.io-server:4.0.1')
```
## Demo project
A basic demo project running on a Jetty server can be found at https://github.com/oddmario/socket.io-server-java-demo
## License
Apache 2.0
================================================
FILE: codecov.yml
================================================
codecov:
notify:
require_ci_to_pass: yes
coverage:
precision: 2
round: down
status:
project: yes
patch: yes
changes: no
parsers:
gcov:
branch_detection:
conditional: yes
loop: yes
method: no
macro: no
comment:
layout: "header, diff"
behavior: default
require_changes: no
================================================
FILE: deploy-docs.sh
================================================
#!/bin/bash
# This script builds the documentation and pushes it to github
GH_REPO_REF="github.com/$TRAVIS_REPO_SLUG"
set -e
JAVADOC_DIR=$(pwd)/target/site/apidocs
# Build the javadocs
mvn clean javadoc:aggregate
# Activate virtualenv
python3 -m venv venv3
. venv3/bin/activate
cd docs
rm -rf ./_build
# Install dependencies
pip install -r requirements.txt
# Build docs
make html
cd _build
# Clone docs branch
git clone -b gh-pages https://git@$GH_REPO_REF docs-upload
cd docs-upload
# Configure git
git config push.default simple
git config user.name "Travis CI"
git config user.email "travis@travis-ci.org"
# Remove existing docs
rm -rf *
# Copy docs from build to repo
cp -r ../html/* ./
# Copy javadocs
rm -rf ./javadocs
mv "$JAVADOC_DIR" ./javadocs
echo "" > .nojekyll
if [[ -f "index.html" ]]; then
# Commit
git add --all
git commit -m "Deploy code docs to GitHub Pages Travis build"
export GH_REPO_TOKEN="trinopoty@${GH_TOKEN}"
git push --force "https://${GH_REPO_TOKEN}@${GH_REPO_REF}" > /dev/null 2>&1
else
echo 'ERROR: index.html not found'
exit 1
fi
================================================
FILE: docs/.gitignore
================================================
/_build
================================================
FILE: docs/Makefile
================================================
# Minimal makefile for Sphinx documentation
#
# You can set these variables from the command line.
SPHINXOPTS =
SPHINXBUILD = sphinx-build
SPHINXPROJ = SocketIO
SOURCEDIR = .
BUILDDIR = _build
# Put it first so that "make" without argument is like "make help".
help:
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
.PHONY: help Makefile
# Catch-all target: route all unknown targets to Sphinx using the new
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
%: Makefile
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
================================================
FILE: docs/_static/.keep
================================================
================================================
FILE: docs/_themes/sphinx_rtd_theme/__init__.py
================================================
"""Sphinx ReadTheDocs theme.
From https://github.com/ryan-roemer/sphinx-bootstrap-theme.
"""
from os import path
__version__ = '0.4.1'
__version_full__ = __version__
def get_html_theme_path():
"""Return list of HTML theme paths."""
cur_dir = path.abspath(path.dirname(path.dirname(__file__)))
return cur_dir
# See http://www.sphinx-doc.org/en/stable/theming.html#distribute-your-theme-as-a-python-package
def setup(app):
app.add_html_theme('sphinx_rtd_theme', path.abspath(path.dirname(__file__)))
================================================
FILE: docs/_themes/sphinx_rtd_theme/breadcrumbs.html
================================================
{# Support for Sphinx 1.3+ page_source_suffix, but don't break old builds. #}
{% if page_source_suffix %}
{% set suffix = page_source_suffix %}
{% else %}
{% set suffix = source_suffix %}
{% endif %}
{% if meta is defined and meta is not none %}
{% set check_meta = True %}
{% else %}
{% set check_meta = False %}
{% endif %}
{% if check_meta and 'github_url' in meta %}
{% set display_github = True %}
{% endif %}
{% if check_meta and 'bitbucket_url' in meta %}
{% set display_bitbucket = True %}
{% endif %}
{% if check_meta and 'gitlab_url' in meta %}
{% set display_gitlab = True %}
{% endif %}
{% if (theme_prev_next_buttons_location == 'top' or theme_prev_next_buttons_location == 'both') and (next or prev) %}
{% endif %}
================================================
FILE: docs/_themes/sphinx_rtd_theme/footer.html
================================================
================================================
FILE: docs/_themes/sphinx_rtd_theme/layout.html
================================================
{# TEMPLATE VAR SETTINGS #}
{%- set url_root = pathto('', 1) %}
{%- if url_root == '#' %}{% set url_root = '' %}{% endif %}
{%- if not embedded and docstitle %}
{%- set titlesuffix = " — "|safe + docstitle|e %}
{%- else %}
{%- set titlesuffix = "" %}
{%- endif %}
{%- set lang_attr = 'en' if language == None else (language | replace('_', '-')) %}
{{ metatags }}
{% block htmltitle %}
{{ title|striptags|e }}{{ titlesuffix }}
{% endblock %}
{# FAVICON #}
{% if favicon %}
{% endif %}
{# CANONICAL URL #}
{% if theme_canonical_url %}
{% endif %}
{# CSS #}
{# OPENSEARCH #}
{% if not embedded %}
{% if use_opensearch %}
{% endif %}
{% endif %}
{%- for css in css_files %}
{%- if css|attr("rel") %}
{%- else %}
{%- endif %}
{%- endfor %}
{%- for cssfile in extra_css_files %}
{%- endfor %}
{%- block linktags %}
{%- if hasdoc('about') %}
{%- endif %}
{%- if hasdoc('genindex') %}
{%- endif %}
{%- if hasdoc('search') %}
{%- endif %}
{%- if hasdoc('copyright') %}
{%- endif %}
{%- if next %}
{%- endif %}
{%- if prev %}
{%- endif %}
{%- endblock %}
{%- block extrahead %} {% endblock %}
{# Keep modernizr in head - http://modernizr.com/docs/#installing #}
{% block extrabody %} {% endblock %}
{# SIDE NAV, TOGGLES ON MOBILE #}
{# MOBILE NAV, TRIGGLES SIDE NAV ON TOGGLE #}
{% block mobile_nav %}
{{ project }}
{% endblock %}
{%- block content %}
{% if theme_style_external_links|tobool %}
{% else %}
{% endif %}
{% include "breadcrumbs.html" %}
{%- block document %}
{% block body %}{% endblock %}
{% if self.comments()|trim %}
{% block comments %}{% endblock %}
{% endif%}
{%- endblock %}
{% include "footer.html" %}
{%- endblock %}
{% include "versions.html" %}
{% if not embedded %}
{%- for scriptfile in script_files %}
{%- endfor %}
{% endif %}
{%- block footer %} {% endblock %}
================================================
FILE: docs/_themes/sphinx_rtd_theme/search.html
================================================
{#
basic/search.html
~~~~~~~~~~~~~~~~~
Template for the search page.
:copyright: Copyright 2007-2013 by the Sphinx team, see AUTHORS.
:license: BSD, see LICENSE for details.
#}
{%- extends "layout.html" %}
{% set title = _('Search') %}
{% set script_files = script_files + ['_static/searchtools.js'] %}
{% block footer %}
{# this is used when loading the search index using $.ajax fails,
such as on Chrome for documents on localhost #}
{{ super() }}
{% endblock %}
{% block body %}
{% trans %}Please activate JavaScript to enable the search
functionality.{% endtrans %}
{% if search_performed %}
{{ _('Search Results') }}
{% if not search_results %}
{{ _('Your search did not match any documents. Please make sure that all words are spelled correctly and that you\'ve selected enough categories.') }}
{% endif %}
{% endif %}
{% if search_results %}
{% for href, caption, context in search_results %}
{{ caption }}
{{ context|e }}
{% endfor %}
{% endif %}
{% endblock %}
================================================
FILE: docs/_themes/sphinx_rtd_theme/searchbox.html
================================================
{%- if builder != 'singlehtml' %}
{%- endif %}
================================================
FILE: docs/_themes/sphinx_rtd_theme/static/css/badge_only.css
================================================
.fa:before{-webkit-font-smoothing:antialiased}.clearfix{*zoom:1}.clearfix:before,.clearfix:after{display:table;content:""}.clearfix:after{clear:both}@font-face{font-family:FontAwesome;font-weight:normal;font-style:normal;src:url("../fonts/fontawesome-webfont.eot");src:url("../fonts/fontawesome-webfont.eot?#iefix") format("embedded-opentype"),url("../fonts/fontawesome-webfont.woff") format("woff"),url("../fonts/fontawesome-webfont.ttf") format("truetype"),url("../fonts/fontawesome-webfont.svg#FontAwesome") format("svg")}.fa:before{display:inline-block;font-family:FontAwesome;font-style:normal;font-weight:normal;line-height:1;text-decoration:inherit}a .fa{display:inline-block;text-decoration:inherit}li .fa{display:inline-block}li .fa-large:before,li .fa-large:before{width:1.875em}ul.fas{list-style-type:none;margin-left:2em;text-indent:-0.8em}ul.fas li .fa{width:.8em}ul.fas li .fa-large:before,ul.fas li .fa-large:before{vertical-align:baseline}.fa-book:before{content:""}.icon-book:before{content:""}.fa-caret-down:before{content:""}.icon-caret-down:before{content:""}.fa-caret-up:before{content:""}.icon-caret-up:before{content:""}.fa-caret-left:before{content:""}.icon-caret-left:before{content:""}.fa-caret-right:before{content:""}.icon-caret-right:before{content:""}.rst-versions{position:fixed;bottom:0;left:0;width:300px;color:#fcfcfc;background:#1f1d1d;font-family:"Lato","proxima-nova","Helvetica Neue",Arial,sans-serif;z-index:400}.rst-versions a{color:#2980B9;text-decoration:none}.rst-versions .rst-badge-small{display:none}.rst-versions .rst-current-version{padding:12px;background-color:#272525;display:block;text-align:right;font-size:90%;cursor:pointer;color:#27AE60;*zoom:1}.rst-versions .rst-current-version:before,.rst-versions .rst-current-version:after{display:table;content:""}.rst-versions .rst-current-version:after{clear:both}.rst-versions .rst-current-version .fa{color:#fcfcfc}.rst-versions .rst-current-version .fa-book{float:left}.rst-versions .rst-current-version .icon-book{float:left}.rst-versions .rst-current-version.rst-out-of-date{background-color:#E74C3C;color:#fff}.rst-versions .rst-current-version.rst-active-old-version{background-color:#F1C40F;color:#000}.rst-versions.shift-up{height:auto;max-height:100%}.rst-versions.shift-up .rst-other-versions{display:block}.rst-versions .rst-other-versions{font-size:90%;padding:12px;color:gray;display:none}.rst-versions .rst-other-versions hr{display:block;height:1px;border:0;margin:20px 0;padding:0;border-top:solid 1px #413d3d}.rst-versions .rst-other-versions dd{display:inline-block;margin:0}.rst-versions .rst-other-versions dd a{display:inline-block;padding:6px;color:#fcfcfc}.rst-versions.rst-badge{width:auto;bottom:20px;right:20px;left:auto;border:none;max-width:300px}.rst-versions.rst-badge .icon-book{float:none}.rst-versions.rst-badge .fa-book{float:none}.rst-versions.rst-badge.shift-up .rst-current-version{text-align:right}.rst-versions.rst-badge.shift-up .rst-current-version .fa-book{float:left}.rst-versions.rst-badge.shift-up .rst-current-version .icon-book{float:left}.rst-versions.rst-badge .rst-current-version{width:auto;height:30px;line-height:30px;padding:0 6px;display:block;text-align:center}@media screen and (max-width: 768px){.rst-versions{width:85%;display:none}.rst-versions.shift{display:block}}
================================================
FILE: docs/_themes/sphinx_rtd_theme/static/css/theme.css
================================================
/* sphinx_rtd_theme version 0.4.1 | MIT license */
/* Built 20180727 10:07 */
*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}article,aside,details,figcaption,figure,footer,header,hgroup,nav,section{display:block}audio,canvas,video{display:inline-block;*display:inline;*zoom:1}audio:not([controls]){display:none}[hidden]{display:none}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}html{font-size:100%;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}a:hover,a:active{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:bold}blockquote{margin:0}dfn{font-style:italic}ins{background:#ff9;color:#000;text-decoration:none}mark{background:#ff0;color:#000;font-style:italic;font-weight:bold}pre,code,.rst-content tt,.rst-content code,kbd,samp{font-family:monospace,serif;_font-family:"courier new",monospace;font-size:1em}pre{white-space:pre}q{quotes:none}q:before,q:after{content:"";content:none}small{font-size:85%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-0.5em}sub{bottom:-0.25em}ul,ol,dl{margin:0;padding:0;list-style:none;list-style-image:none}li{list-style:none}dd{margin:0}img{border:0;-ms-interpolation-mode:bicubic;vertical-align:middle;max-width:100%}svg:not(:root){overflow:hidden}figure{margin:0}form{margin:0}fieldset{border:0;margin:0;padding:0}label{cursor:pointer}legend{border:0;*margin-left:-7px;padding:0;white-space:normal}button,input,select,textarea{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle}button,input{line-height:normal}button,input[type="button"],input[type="reset"],input[type="submit"]{cursor:pointer;-webkit-appearance:button;*overflow:visible}button[disabled],input[disabled]{cursor:default}input[type="checkbox"],input[type="radio"]{box-sizing:border-box;padding:0;*width:13px;*height:13px}input[type="search"]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}input[type="search"]::-webkit-search-decoration,input[type="search"]::-webkit-search-cancel-button{-webkit-appearance:none}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}textarea{overflow:auto;vertical-align:top;resize:vertical}table{border-collapse:collapse;border-spacing:0}td{vertical-align:top}.chromeframe{margin:.2em 0;background:#ccc;color:#000;padding:.2em 0}.ir{display:block;border:0;text-indent:-999em;overflow:hidden;background-color:transparent;background-repeat:no-repeat;text-align:left;direction:ltr;*line-height:0}.ir br{display:none}.hidden{display:none !important;visibility:hidden}.visuallyhidden{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.visuallyhidden.focusable:active,.visuallyhidden.focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}.invisible{visibility:hidden}.relative{position:relative}big,small{font-size:100%}@media print{html,body,section{background:none !important}*{box-shadow:none !important;text-shadow:none !important;filter:none !important;-ms-filter:none !important}a,a:visited{text-decoration:underline}.ir a:after,a[href^="javascript:"]:after,a[href^="#"]:after{content:""}pre,blockquote{page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100% !important}@page{margin:.5cm}p,h2,.rst-content .toctree-wrapper p.caption,h3{orphans:3;widows:3}h2,.rst-content .toctree-wrapper p.caption,h3{page-break-after:avoid}}.fa:before,.wy-menu-vertical li span.toctree-expand:before,.wy-menu-vertical li.on a span.toctree-expand:before,.wy-menu-vertical li.current>a span.toctree-expand:before,.rst-content .admonition-title:before,.rst-content h1 .headerlink:before,.rst-content h2 .headerlink:before,.rst-content h3 .headerlink:before,.rst-content h4 .headerlink:before,.rst-content h5 .headerlink:before,.rst-content h6 .headerlink:before,.rst-content dl dt .headerlink:before,.rst-content p.caption .headerlink:before,.rst-content table>caption .headerlink:before,.rst-content tt.download span:first-child:before,.rst-content code.download span:first-child:before,.icon:before,.wy-dropdown .caret:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.wy-alert,.rst-content .note,.rst-content .attention,.rst-content .caution,.rst-content .danger,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .tip,.rst-content .warning,.rst-content .seealso,.rst-content .admonition-todo,.rst-content .admonition,.btn,input[type="text"],input[type="password"],input[type="email"],input[type="url"],input[type="date"],input[type="month"],input[type="time"],input[type="datetime"],input[type="datetime-local"],input[type="week"],input[type="number"],input[type="search"],input[type="tel"],input[type="color"],select,textarea,.wy-menu-vertical li.on a,.wy-menu-vertical li.current>a,.wy-side-nav-search>a,.wy-side-nav-search .wy-dropdown>a,.wy-nav-top a{-webkit-font-smoothing:antialiased}.clearfix{*zoom:1}.clearfix:before,.clearfix:after{display:table;content:""}.clearfix:after{clear:both}/*!
* Font Awesome 4.7.0 by @davegandy - http://fontawesome.io - @fontawesome
* License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License)
*/@font-face{font-family:'FontAwesome';src:url("../fonts/fontawesome-webfont.eot?v=4.7.0");src:url("../fonts/fontawesome-webfont.eot?#iefix&v=4.7.0") format("embedded-opentype"),url("../fonts/fontawesome-webfont.woff2?v=4.7.0") format("woff2"),url("../fonts/fontawesome-webfont.woff?v=4.7.0") format("woff"),url("../fonts/fontawesome-webfont.ttf?v=4.7.0") format("truetype"),url("../fonts/fontawesome-webfont.svg?v=4.7.0#fontawesomeregular") format("svg");font-weight:normal;font-style:normal}.fa,.wy-menu-vertical li span.toctree-expand,.wy-menu-vertical li.on a span.toctree-expand,.wy-menu-vertical li.current>a span.toctree-expand,.rst-content .admonition-title,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content dl dt .headerlink,.rst-content p.caption .headerlink,.rst-content table>caption .headerlink,.rst-content tt.download span:first-child,.rst-content code.download span:first-child,.icon{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.3333333333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.2857142857em;text-align:center}.fa-ul{padding-left:0;margin-left:2.1428571429em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.1428571429em;width:2.1428571429em;top:.1428571429em;text-align:center}.fa-li.fa-lg{left:-1.8571428571em}.fa-border{padding:.2em .25em .15em;border:solid 0.08em #eee;border-radius:.1em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa.fa-pull-left,.wy-menu-vertical li span.fa-pull-left.toctree-expand,.wy-menu-vertical li.on a span.fa-pull-left.toctree-expand,.wy-menu-vertical li.current>a span.fa-pull-left.toctree-expand,.rst-content .fa-pull-left.admonition-title,.rst-content h1 .fa-pull-left.headerlink,.rst-content h2 .fa-pull-left.headerlink,.rst-content h3 .fa-pull-left.headerlink,.rst-content h4 .fa-pull-left.headerlink,.rst-content h5 .fa-pull-left.headerlink,.rst-content h6 .fa-pull-left.headerlink,.rst-content dl dt .fa-pull-left.headerlink,.rst-content p.caption .fa-pull-left.headerlink,.rst-content table>caption .fa-pull-left.headerlink,.rst-content tt.download span.fa-pull-left:first-child,.rst-content code.download span.fa-pull-left:first-child,.fa-pull-left.icon{margin-right:.3em}.fa.fa-pull-right,.wy-menu-vertical li span.fa-pull-right.toctree-expand,.wy-menu-vertical li.on a span.fa-pull-right.toctree-expand,.wy-menu-vertical li.current>a span.fa-pull-right.toctree-expand,.rst-content .fa-pull-right.admonition-title,.rst-content h1 .fa-pull-right.headerlink,.rst-content h2 .fa-pull-right.headerlink,.rst-content h3 .fa-pull-right.headerlink,.rst-content h4 .fa-pull-right.headerlink,.rst-content h5 .fa-pull-right.headerlink,.rst-content h6 .fa-pull-right.headerlink,.rst-content dl dt .fa-pull-right.headerlink,.rst-content p.caption .fa-pull-right.headerlink,.rst-content table>caption .fa-pull-right.headerlink,.rst-content tt.download span.fa-pull-right:first-child,.rst-content code.download span.fa-pull-right:first-child,.fa-pull-right.icon{margin-left:.3em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left,.wy-menu-vertical li span.pull-left.toctree-expand,.wy-menu-vertical li.on a span.pull-left.toctree-expand,.wy-menu-vertical li.current>a span.pull-left.toctree-expand,.rst-content .pull-left.admonition-title,.rst-content h1 .pull-left.headerlink,.rst-content h2 .pull-left.headerlink,.rst-content h3 .pull-left.headerlink,.rst-content h4 .pull-left.headerlink,.rst-content h5 .pull-left.headerlink,.rst-content h6 .pull-left.headerlink,.rst-content dl dt .pull-left.headerlink,.rst-content p.caption .pull-left.headerlink,.rst-content table>caption .pull-left.headerlink,.rst-content tt.download span.pull-left:first-child,.rst-content code.download span.pull-left:first-child,.pull-left.icon{margin-right:.3em}.fa.pull-right,.wy-menu-vertical li span.pull-right.toctree-expand,.wy-menu-vertical li.on a span.pull-right.toctree-expand,.wy-menu-vertical li.current>a span.pull-right.toctree-expand,.rst-content .pull-right.admonition-title,.rst-content h1 .pull-right.headerlink,.rst-content h2 .pull-right.headerlink,.rst-content h3 .pull-right.headerlink,.rst-content h4 .pull-right.headerlink,.rst-content h5 .pull-right.headerlink,.rst-content h6 .pull-right.headerlink,.rst-content dl dt .pull-right.headerlink,.rst-content p.caption .pull-right.headerlink,.rst-content table>caption .pull-right.headerlink,.rst-content tt.download span.pull-right:first-child,.rst-content code.download span.pull-right:first-child,.pull-right.icon{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s infinite linear;animation:fa-spin 2s infinite linear}.fa-pulse{-webkit-animation:fa-spin 1s infinite steps(8);animation:fa-spin 1s infinite steps(8)}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";-webkit-transform:scale(-1, 1);-ms-transform:scale(-1, 1);transform:scale(-1, 1)}.fa-flip-vertical{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)";-webkit-transform:scale(1, -1);-ms-transform:scale(1, -1);transform:scale(1, -1)}:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270,:root .fa-flip-horizontal,:root .fa-flip-vertical{filter:none}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:""}.fa-music:before{content:""}.fa-search:before,.icon-search:before{content:""}.fa-envelope-o:before{content:""}.fa-heart:before{content:""}.fa-star:before{content:""}.fa-star-o:before{content:""}.fa-user:before{content:""}.fa-film:before{content:""}.fa-th-large:before{content:""}.fa-th:before{content:""}.fa-th-list:before{content:""}.fa-check:before{content:""}.fa-remove:before,.fa-close:before,.fa-times:before{content:""}.fa-search-plus:before{content:""}.fa-search-minus:before{content:""}.fa-power-off:before{content:""}.fa-signal:before{content:""}.fa-gear:before,.fa-cog:before{content:""}.fa-trash-o:before{content:""}.fa-home:before,.icon-home:before{content:""}.fa-file-o:before{content:""}.fa-clock-o:before{content:""}.fa-road:before{content:""}.fa-download:before,.rst-content tt.download span:first-child:before,.rst-content code.download span:first-child:before{content:""}.fa-arrow-circle-o-down:before{content:""}.fa-arrow-circle-o-up:before{content:""}.fa-inbox:before{content:""}.fa-play-circle-o:before{content:""}.fa-rotate-right:before,.fa-repeat:before{content:""}.fa-refresh:before{content:""}.fa-list-alt:before{content:""}.fa-lock:before{content:""}.fa-flag:before{content:""}.fa-headphones:before{content:""}.fa-volume-off:before{content:""}.fa-volume-down:before{content:""}.fa-volume-up:before{content:""}.fa-qrcode:before{content:""}.fa-barcode:before{content:""}.fa-tag:before{content:""}.fa-tags:before{content:""}.fa-book:before,.icon-book:before{content:""}.fa-bookmark:before{content:""}.fa-print:before{content:""}.fa-camera:before{content:""}.fa-font:before{content:""}.fa-bold:before{content:""}.fa-italic:before{content:""}.fa-text-height:before{content:""}.fa-text-width:before{content:""}.fa-align-left:before{content:""}.fa-align-center:before{content:""}.fa-align-right:before{content:""}.fa-align-justify:before{content:""}.fa-list:before{content:""}.fa-dedent:before,.fa-outdent:before{content:""}.fa-indent:before{content:""}.fa-video-camera:before{content:""}.fa-photo:before,.fa-image:before,.fa-picture-o:before{content:""}.fa-pencil:before{content:""}.fa-map-marker:before{content:""}.fa-adjust:before{content:""}.fa-tint:before{content:""}.fa-edit:before,.fa-pencil-square-o:before{content:""}.fa-share-square-o:before{content:""}.fa-check-square-o:before{content:""}.fa-arrows:before{content:""}.fa-step-backward:before{content:""}.fa-fast-backward:before{content:""}.fa-backward:before{content:""}.fa-play:before{content:""}.fa-pause:before{content:""}.fa-stop:before{content:""}.fa-forward:before{content:""}.fa-fast-forward:before{content:""}.fa-step-forward:before{content:""}.fa-eject:before{content:""}.fa-chevron-left:before{content:""}.fa-chevron-right:before{content:""}.fa-plus-circle:before{content:""}.fa-minus-circle:before{content:""}.fa-times-circle:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before{content:""}.fa-check-circle:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before{content:""}.fa-question-circle:before{content:""}.fa-info-circle:before{content:""}.fa-crosshairs:before{content:""}.fa-times-circle-o:before{content:""}.fa-check-circle-o:before{content:""}.fa-ban:before{content:""}.fa-arrow-left:before{content:""}.fa-arrow-right:before{content:""}.fa-arrow-up:before{content:""}.fa-arrow-down:before{content:""}.fa-mail-forward:before,.fa-share:before{content:""}.fa-expand:before{content:""}.fa-compress:before{content:""}.fa-plus:before{content:""}.fa-minus:before{content:""}.fa-asterisk:before{content:""}.fa-exclamation-circle:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.rst-content .admonition-title:before{content:""}.fa-gift:before{content:""}.fa-leaf:before{content:""}.fa-fire:before,.icon-fire:before{content:""}.fa-eye:before{content:""}.fa-eye-slash:before{content:""}.fa-warning:before,.fa-exclamation-triangle:before{content:""}.fa-plane:before{content:""}.fa-calendar:before{content:""}.fa-random:before{content:""}.fa-comment:before{content:""}.fa-magnet:before{content:""}.fa-chevron-up:before{content:""}.fa-chevron-down:before{content:""}.fa-retweet:before{content:""}.fa-shopping-cart:before{content:""}.fa-folder:before{content:""}.fa-folder-open:before{content:""}.fa-arrows-v:before{content:""}.fa-arrows-h:before{content:""}.fa-bar-chart-o:before,.fa-bar-chart:before{content:""}.fa-twitter-square:before{content:""}.fa-facebook-square:before{content:""}.fa-camera-retro:before{content:""}.fa-key:before{content:""}.fa-gears:before,.fa-cogs:before{content:""}.fa-comments:before{content:""}.fa-thumbs-o-up:before{content:""}.fa-thumbs-o-down:before{content:""}.fa-star-half:before{content:""}.fa-heart-o:before{content:""}.fa-sign-out:before{content:""}.fa-linkedin-square:before{content:""}.fa-thumb-tack:before{content:""}.fa-external-link:before{content:""}.fa-sign-in:before{content:""}.fa-trophy:before{content:""}.fa-github-square:before{content:""}.fa-upload:before{content:""}.fa-lemon-o:before{content:""}.fa-phone:before{content:""}.fa-square-o:before{content:""}.fa-bookmark-o:before{content:""}.fa-phone-square:before{content:""}.fa-twitter:before{content:""}.fa-facebook-f:before,.fa-facebook:before{content:""}.fa-github:before,.icon-github:before{content:""}.fa-unlock:before{content:""}.fa-credit-card:before{content:""}.fa-feed:before,.fa-rss:before{content:""}.fa-hdd-o:before{content:""}.fa-bullhorn:before{content:""}.fa-bell:before{content:""}.fa-certificate:before{content:""}.fa-hand-o-right:before{content:""}.fa-hand-o-left:before{content:""}.fa-hand-o-up:before{content:""}.fa-hand-o-down:before{content:""}.fa-arrow-circle-left:before,.icon-circle-arrow-left:before{content:""}.fa-arrow-circle-right:before,.icon-circle-arrow-right:before{content:""}.fa-arrow-circle-up:before{content:""}.fa-arrow-circle-down:before{content:""}.fa-globe:before{content:""}.fa-wrench:before{content:""}.fa-tasks:before{content:""}.fa-filter:before{content:""}.fa-briefcase:before{content:""}.fa-arrows-alt:before{content:""}.fa-group:before,.fa-users:before{content:""}.fa-chain:before,.fa-link:before,.icon-link:before{content:""}.fa-cloud:before{content:""}.fa-flask:before{content:""}.fa-cut:before,.fa-scissors:before{content:""}.fa-copy:before,.fa-files-o:before{content:""}.fa-paperclip:before{content:""}.fa-save:before,.fa-floppy-o:before{content:""}.fa-square:before{content:""}.fa-navicon:before,.fa-reorder:before,.fa-bars:before{content:""}.fa-list-ul:before{content:""}.fa-list-ol:before{content:""}.fa-strikethrough:before{content:""}.fa-underline:before{content:""}.fa-table:before{content:""}.fa-magic:before{content:""}.fa-truck:before{content:""}.fa-pinterest:before{content:""}.fa-pinterest-square:before{content:""}.fa-google-plus-square:before{content:""}.fa-google-plus:before{content:""}.fa-money:before{content:""}.fa-caret-down:before,.wy-dropdown .caret:before,.icon-caret-down:before{content:""}.fa-caret-up:before{content:""}.fa-caret-left:before{content:""}.fa-caret-right:before{content:""}.fa-columns:before{content:""}.fa-unsorted:before,.fa-sort:before{content:""}.fa-sort-down:before,.fa-sort-desc:before{content:""}.fa-sort-up:before,.fa-sort-asc:before{content:""}.fa-envelope:before{content:""}.fa-linkedin:before{content:""}.fa-rotate-left:before,.fa-undo:before{content:""}.fa-legal:before,.fa-gavel:before{content:""}.fa-dashboard:before,.fa-tachometer:before{content:""}.fa-comment-o:before{content:""}.fa-comments-o:before{content:""}.fa-flash:before,.fa-bolt:before{content:""}.fa-sitemap:before{content:""}.fa-umbrella:before{content:""}.fa-paste:before,.fa-clipboard:before{content:""}.fa-lightbulb-o:before{content:""}.fa-exchange:before{content:""}.fa-cloud-download:before{content:""}.fa-cloud-upload:before{content:""}.fa-user-md:before{content:""}.fa-stethoscope:before{content:""}.fa-suitcase:before{content:""}.fa-bell-o:before{content:""}.fa-coffee:before{content:""}.fa-cutlery:before{content:""}.fa-file-text-o:before{content:""}.fa-building-o:before{content:""}.fa-hospital-o:before{content:""}.fa-ambulance:before{content:""}.fa-medkit:before{content:""}.fa-fighter-jet:before{content:""}.fa-beer:before{content:""}.fa-h-square:before{content:""}.fa-plus-square:before{content:""}.fa-angle-double-left:before{content:""}.fa-angle-double-right:before{content:""}.fa-angle-double-up:before{content:""}.fa-angle-double-down:before{content:""}.fa-angle-left:before{content:""}.fa-angle-right:before{content:""}.fa-angle-up:before{content:""}.fa-angle-down:before{content:""}.fa-desktop:before{content:""}.fa-laptop:before{content:""}.fa-tablet:before{content:""}.fa-mobile-phone:before,.fa-mobile:before{content:""}.fa-circle-o:before{content:""}.fa-quote-left:before{content:""}.fa-quote-right:before{content:""}.fa-spinner:before{content:""}.fa-circle:before{content:""}.fa-mail-reply:before,.fa-reply:before{content:""}.fa-github-alt:before{content:""}.fa-folder-o:before{content:""}.fa-folder-open-o:before{content:""}.fa-smile-o:before{content:""}.fa-frown-o:before{content:""}.fa-meh-o:before{content:""}.fa-gamepad:before{content:""}.fa-keyboard-o:before{content:""}.fa-flag-o:before{content:""}.fa-flag-checkered:before{content:""}.fa-terminal:before{content:""}.fa-code:before{content:""}.fa-mail-reply-all:before,.fa-reply-all:before{content:""}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:""}.fa-location-arrow:before{content:""}.fa-crop:before{content:""}.fa-code-fork:before{content:""}.fa-unlink:before,.fa-chain-broken:before{content:""}.fa-question:before{content:""}.fa-info:before{content:""}.fa-exclamation:before{content:""}.fa-superscript:before{content:""}.fa-subscript:before{content:""}.fa-eraser:before{content:""}.fa-puzzle-piece:before{content:""}.fa-microphone:before{content:""}.fa-microphone-slash:before{content:""}.fa-shield:before{content:""}.fa-calendar-o:before{content:""}.fa-fire-extinguisher:before{content:""}.fa-rocket:before{content:""}.fa-maxcdn:before{content:""}.fa-chevron-circle-left:before{content:""}.fa-chevron-circle-right:before{content:""}.fa-chevron-circle-up:before{content:""}.fa-chevron-circle-down:before{content:""}.fa-html5:before{content:""}.fa-css3:before{content:""}.fa-anchor:before{content:""}.fa-unlock-alt:before{content:""}.fa-bullseye:before{content:""}.fa-ellipsis-h:before{content:""}.fa-ellipsis-v:before{content:""}.fa-rss-square:before{content:""}.fa-play-circle:before{content:""}.fa-ticket:before{content:""}.fa-minus-square:before{content:""}.fa-minus-square-o:before,.wy-menu-vertical li.on a span.toctree-expand:before,.wy-menu-vertical li.current>a span.toctree-expand:before{content:""}.fa-level-up:before{content:""}.fa-level-down:before{content:""}.fa-check-square:before{content:""}.fa-pencil-square:before{content:""}.fa-external-link-square:before{content:""}.fa-share-square:before{content:""}.fa-compass:before{content:""}.fa-toggle-down:before,.fa-caret-square-o-down:before{content:""}.fa-toggle-up:before,.fa-caret-square-o-up:before{content:""}.fa-toggle-right:before,.fa-caret-square-o-right:before{content:""}.fa-euro:before,.fa-eur:before{content:""}.fa-gbp:before{content:""}.fa-dollar:before,.fa-usd:before{content:""}.fa-rupee:before,.fa-inr:before{content:""}.fa-cny:before,.fa-rmb:before,.fa-yen:before,.fa-jpy:before{content:""}.fa-ruble:before,.fa-rouble:before,.fa-rub:before{content:""}.fa-won:before,.fa-krw:before{content:""}.fa-bitcoin:before,.fa-btc:before{content:""}.fa-file:before{content:""}.fa-file-text:before{content:""}.fa-sort-alpha-asc:before{content:""}.fa-sort-alpha-desc:before{content:""}.fa-sort-amount-asc:before{content:""}.fa-sort-amount-desc:before{content:""}.fa-sort-numeric-asc:before{content:""}.fa-sort-numeric-desc:before{content:""}.fa-thumbs-up:before{content:""}.fa-thumbs-down:before{content:""}.fa-youtube-square:before{content:""}.fa-youtube:before{content:""}.fa-xing:before{content:""}.fa-xing-square:before{content:""}.fa-youtube-play:before{content:""}.fa-dropbox:before{content:""}.fa-stack-overflow:before{content:""}.fa-instagram:before{content:""}.fa-flickr:before{content:""}.fa-adn:before{content:""}.fa-bitbucket:before,.icon-bitbucket:before{content:""}.fa-bitbucket-square:before{content:""}.fa-tumblr:before{content:""}.fa-tumblr-square:before{content:""}.fa-long-arrow-down:before{content:""}.fa-long-arrow-up:before{content:""}.fa-long-arrow-left:before{content:""}.fa-long-arrow-right:before{content:""}.fa-apple:before{content:""}.fa-windows:before{content:""}.fa-android:before{content:""}.fa-linux:before{content:""}.fa-dribbble:before{content:""}.fa-skype:before{content:""}.fa-foursquare:before{content:""}.fa-trello:before{content:""}.fa-female:before{content:""}.fa-male:before{content:""}.fa-gittip:before,.fa-gratipay:before{content:""}.fa-sun-o:before{content:""}.fa-moon-o:before{content:""}.fa-archive:before{content:""}.fa-bug:before{content:""}.fa-vk:before{content:""}.fa-weibo:before{content:""}.fa-renren:before{content:""}.fa-pagelines:before{content:""}.fa-stack-exchange:before{content:""}.fa-arrow-circle-o-right:before{content:""}.fa-arrow-circle-o-left:before{content:""}.fa-toggle-left:before,.fa-caret-square-o-left:before{content:""}.fa-dot-circle-o:before{content:""}.fa-wheelchair:before{content:""}.fa-vimeo-square:before{content:""}.fa-turkish-lira:before,.fa-try:before{content:""}.fa-plus-square-o:before,.wy-menu-vertical li span.toctree-expand:before{content:""}.fa-space-shuttle:before{content:""}.fa-slack:before{content:""}.fa-envelope-square:before{content:""}.fa-wordpress:before{content:""}.fa-openid:before{content:""}.fa-institution:before,.fa-bank:before,.fa-university:before{content:""}.fa-mortar-board:before,.fa-graduation-cap:before{content:""}.fa-yahoo:before{content:""}.fa-google:before{content:""}.fa-reddit:before{content:""}.fa-reddit-square:before{content:""}.fa-stumbleupon-circle:before{content:""}.fa-stumbleupon:before{content:""}.fa-delicious:before{content:""}.fa-digg:before{content:""}.fa-pied-piper-pp:before{content:""}.fa-pied-piper-alt:before{content:""}.fa-drupal:before{content:""}.fa-joomla:before{content:""}.fa-language:before{content:""}.fa-fax:before{content:""}.fa-building:before{content:""}.fa-child:before{content:""}.fa-paw:before{content:""}.fa-spoon:before{content:""}.fa-cube:before{content:""}.fa-cubes:before{content:""}.fa-behance:before{content:""}.fa-behance-square:before{content:""}.fa-steam:before{content:""}.fa-steam-square:before{content:""}.fa-recycle:before{content:""}.fa-automobile:before,.fa-car:before{content:""}.fa-cab:before,.fa-taxi:before{content:""}.fa-tree:before{content:""}.fa-spotify:before{content:""}.fa-deviantart:before{content:""}.fa-soundcloud:before{content:""}.fa-database:before{content:""}.fa-file-pdf-o:before{content:""}.fa-file-word-o:before{content:""}.fa-file-excel-o:before{content:""}.fa-file-powerpoint-o:before{content:""}.fa-file-photo-o:before,.fa-file-picture-o:before,.fa-file-image-o:before{content:""}.fa-file-zip-o:before,.fa-file-archive-o:before{content:""}.fa-file-sound-o:before,.fa-file-audio-o:before{content:""}.fa-file-movie-o:before,.fa-file-video-o:before{content:""}.fa-file-code-o:before{content:""}.fa-vine:before{content:""}.fa-codepen:before{content:""}.fa-jsfiddle:before{content:""}.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-saver:before,.fa-support:before,.fa-life-ring:before{content:""}.fa-circle-o-notch:before{content:""}.fa-ra:before,.fa-resistance:before,.fa-rebel:before{content:""}.fa-ge:before,.fa-empire:before{content:""}.fa-git-square:before{content:""}.fa-git:before{content:""}.fa-y-combinator-square:before,.fa-yc-square:before,.fa-hacker-news:before{content:""}.fa-tencent-weibo:before{content:""}.fa-qq:before{content:""}.fa-wechat:before,.fa-weixin:before{content:""}.fa-send:before,.fa-paper-plane:before{content:""}.fa-send-o:before,.fa-paper-plane-o:before{content:""}.fa-history:before{content:""}.fa-circle-thin:before{content:""}.fa-header:before{content:""}.fa-paragraph:before{content:""}.fa-sliders:before{content:""}.fa-share-alt:before{content:""}.fa-share-alt-square:before{content:""}.fa-bomb:before{content:""}.fa-soccer-ball-o:before,.fa-futbol-o:before{content:""}.fa-tty:before{content:""}.fa-binoculars:before{content:""}.fa-plug:before{content:""}.fa-slideshare:before{content:""}.fa-twitch:before{content:""}.fa-yelp:before{content:""}.fa-newspaper-o:before{content:""}.fa-wifi:before{content:""}.fa-calculator:before{content:""}.fa-paypal:before{content:""}.fa-google-wallet:before{content:""}.fa-cc-visa:before{content:""}.fa-cc-mastercard:before{content:""}.fa-cc-discover:before{content:""}.fa-cc-amex:before{content:""}.fa-cc-paypal:before{content:""}.fa-cc-stripe:before{content:""}.fa-bell-slash:before{content:""}.fa-bell-slash-o:before{content:""}.fa-trash:before{content:""}.fa-copyright:before{content:""}.fa-at:before{content:""}.fa-eyedropper:before{content:""}.fa-paint-brush:before{content:""}.fa-birthday-cake:before{content:""}.fa-area-chart:before{content:""}.fa-pie-chart:before{content:""}.fa-line-chart:before{content:""}.fa-lastfm:before{content:""}.fa-lastfm-square:before{content:""}.fa-toggle-off:before{content:""}.fa-toggle-on:before{content:""}.fa-bicycle:before{content:""}.fa-bus:before{content:""}.fa-ioxhost:before{content:""}.fa-angellist:before{content:""}.fa-cc:before{content:""}.fa-shekel:before,.fa-sheqel:before,.fa-ils:before{content:""}.fa-meanpath:before{content:""}.fa-buysellads:before{content:""}.fa-connectdevelop:before{content:""}.fa-dashcube:before{content:""}.fa-forumbee:before{content:""}.fa-leanpub:before{content:""}.fa-sellsy:before{content:""}.fa-shirtsinbulk:before{content:""}.fa-simplybuilt:before{content:""}.fa-skyatlas:before{content:""}.fa-cart-plus:before{content:""}.fa-cart-arrow-down:before{content:""}.fa-diamond:before{content:""}.fa-ship:before{content:""}.fa-user-secret:before{content:""}.fa-motorcycle:before{content:""}.fa-street-view:before{content:""}.fa-heartbeat:before{content:""}.fa-venus:before{content:""}.fa-mars:before{content:""}.fa-mercury:before{content:""}.fa-intersex:before,.fa-transgender:before{content:""}.fa-transgender-alt:before{content:""}.fa-venus-double:before{content:""}.fa-mars-double:before{content:""}.fa-venus-mars:before{content:""}.fa-mars-stroke:before{content:""}.fa-mars-stroke-v:before{content:""}.fa-mars-stroke-h:before{content:""}.fa-neuter:before{content:""}.fa-genderless:before{content:""}.fa-facebook-official:before{content:""}.fa-pinterest-p:before{content:""}.fa-whatsapp:before{content:""}.fa-server:before{content:""}.fa-user-plus:before{content:""}.fa-user-times:before{content:""}.fa-hotel:before,.fa-bed:before{content:""}.fa-viacoin:before{content:""}.fa-train:before{content:""}.fa-subway:before{content:""}.fa-medium:before{content:""}.fa-yc:before,.fa-y-combinator:before{content:""}.fa-optin-monster:before{content:""}.fa-opencart:before{content:""}.fa-expeditedssl:before{content:""}.fa-battery-4:before,.fa-battery:before,.fa-battery-full:before{content:""}.fa-battery-3:before,.fa-battery-three-quarters:before{content:""}.fa-battery-2:before,.fa-battery-half:before{content:""}.fa-battery-1:before,.fa-battery-quarter:before{content:""}.fa-battery-0:before,.fa-battery-empty:before{content:""}.fa-mouse-pointer:before{content:""}.fa-i-cursor:before{content:""}.fa-object-group:before{content:""}.fa-object-ungroup:before{content:""}.fa-sticky-note:before{content:""}.fa-sticky-note-o:before{content:""}.fa-cc-jcb:before{content:""}.fa-cc-diners-club:before{content:""}.fa-clone:before{content:""}.fa-balance-scale:before{content:""}.fa-hourglass-o:before{content:""}.fa-hourglass-1:before,.fa-hourglass-start:before{content:""}.fa-hourglass-2:before,.fa-hourglass-half:before{content:""}.fa-hourglass-3:before,.fa-hourglass-end:before{content:""}.fa-hourglass:before{content:""}.fa-hand-grab-o:before,.fa-hand-rock-o:before{content:""}.fa-hand-stop-o:before,.fa-hand-paper-o:before{content:""}.fa-hand-scissors-o:before{content:""}.fa-hand-lizard-o:before{content:""}.fa-hand-spock-o:before{content:""}.fa-hand-pointer-o:before{content:""}.fa-hand-peace-o:before{content:""}.fa-trademark:before{content:""}.fa-registered:before{content:""}.fa-creative-commons:before{content:""}.fa-gg:before{content:""}.fa-gg-circle:before{content:""}.fa-tripadvisor:before{content:""}.fa-odnoklassniki:before{content:""}.fa-odnoklassniki-square:before{content:""}.fa-get-pocket:before{content:""}.fa-wikipedia-w:before{content:""}.fa-safari:before{content:""}.fa-chrome:before{content:""}.fa-firefox:before{content:""}.fa-opera:before{content:""}.fa-internet-explorer:before{content:""}.fa-tv:before,.fa-television:before{content:""}.fa-contao:before{content:""}.fa-500px:before{content:""}.fa-amazon:before{content:""}.fa-calendar-plus-o:before{content:""}.fa-calendar-minus-o:before{content:""}.fa-calendar-times-o:before{content:""}.fa-calendar-check-o:before{content:""}.fa-industry:before{content:""}.fa-map-pin:before{content:""}.fa-map-signs:before{content:""}.fa-map-o:before{content:""}.fa-map:before{content:""}.fa-commenting:before{content:""}.fa-commenting-o:before{content:""}.fa-houzz:before{content:""}.fa-vimeo:before{content:""}.fa-black-tie:before{content:""}.fa-fonticons:before{content:""}.fa-reddit-alien:before{content:""}.fa-edge:before{content:""}.fa-credit-card-alt:before{content:""}.fa-codiepie:before{content:""}.fa-modx:before{content:""}.fa-fort-awesome:before{content:""}.fa-usb:before{content:""}.fa-product-hunt:before{content:""}.fa-mixcloud:before{content:""}.fa-scribd:before{content:""}.fa-pause-circle:before{content:""}.fa-pause-circle-o:before{content:""}.fa-stop-circle:before{content:""}.fa-stop-circle-o:before{content:""}.fa-shopping-bag:before{content:""}.fa-shopping-basket:before{content:""}.fa-hashtag:before{content:""}.fa-bluetooth:before{content:""}.fa-bluetooth-b:before{content:""}.fa-percent:before{content:""}.fa-gitlab:before,.icon-gitlab:before{content:""}.fa-wpbeginner:before{content:""}.fa-wpforms:before{content:""}.fa-envira:before{content:""}.fa-universal-access:before{content:""}.fa-wheelchair-alt:before{content:""}.fa-question-circle-o:before{content:""}.fa-blind:before{content:""}.fa-audio-description:before{content:""}.fa-volume-control-phone:before{content:""}.fa-braille:before{content:""}.fa-assistive-listening-systems:before{content:""}.fa-asl-interpreting:before,.fa-american-sign-language-interpreting:before{content:""}.fa-deafness:before,.fa-hard-of-hearing:before,.fa-deaf:before{content:""}.fa-glide:before{content:""}.fa-glide-g:before{content:""}.fa-signing:before,.fa-sign-language:before{content:""}.fa-low-vision:before{content:""}.fa-viadeo:before{content:""}.fa-viadeo-square:before{content:""}.fa-snapchat:before{content:""}.fa-snapchat-ghost:before{content:""}.fa-snapchat-square:before{content:""}.fa-pied-piper:before{content:""}.fa-first-order:before{content:""}.fa-yoast:before{content:""}.fa-themeisle:before{content:""}.fa-google-plus-circle:before,.fa-google-plus-official:before{content:""}.fa-fa:before,.fa-font-awesome:before{content:""}.fa-handshake-o:before{content:""}.fa-envelope-open:before{content:""}.fa-envelope-open-o:before{content:""}.fa-linode:before{content:""}.fa-address-book:before{content:""}.fa-address-book-o:before{content:""}.fa-vcard:before,.fa-address-card:before{content:""}.fa-vcard-o:before,.fa-address-card-o:before{content:""}.fa-user-circle:before{content:""}.fa-user-circle-o:before{content:""}.fa-user-o:before{content:""}.fa-id-badge:before{content:""}.fa-drivers-license:before,.fa-id-card:before{content:""}.fa-drivers-license-o:before,.fa-id-card-o:before{content:""}.fa-quora:before{content:""}.fa-free-code-camp:before{content:""}.fa-telegram:before{content:""}.fa-thermometer-4:before,.fa-thermometer:before,.fa-thermometer-full:before{content:""}.fa-thermometer-3:before,.fa-thermometer-three-quarters:before{content:""}.fa-thermometer-2:before,.fa-thermometer-half:before{content:""}.fa-thermometer-1:before,.fa-thermometer-quarter:before{content:""}.fa-thermometer-0:before,.fa-thermometer-empty:before{content:""}.fa-shower:before{content:""}.fa-bathtub:before,.fa-s15:before,.fa-bath:before{content:""}.fa-podcast:before{content:""}.fa-window-maximize:before{content:""}.fa-window-minimize:before{content:""}.fa-window-restore:before{content:""}.fa-times-rectangle:before,.fa-window-close:before{content:""}.fa-times-rectangle-o:before,.fa-window-close-o:before{content:""}.fa-bandcamp:before{content:""}.fa-grav:before{content:""}.fa-etsy:before{content:""}.fa-imdb:before{content:""}.fa-ravelry:before{content:""}.fa-eercast:before{content:""}.fa-microchip:before{content:""}.fa-snowflake-o:before{content:""}.fa-superpowers:before{content:""}.fa-wpexplorer:before{content:""}.fa-meetup:before{content:""}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0, 0, 0, 0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto}.fa,.wy-menu-vertical li span.toctree-expand,.wy-menu-vertical li.on a span.toctree-expand,.wy-menu-vertical li.current>a span.toctree-expand,.rst-content .admonition-title,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content dl dt .headerlink,.rst-content p.caption .headerlink,.rst-content table>caption .headerlink,.rst-content tt.download span:first-child,.rst-content code.download span:first-child,.icon,.wy-dropdown .caret,.wy-inline-validate.wy-inline-validate-success .wy-input-context,.wy-inline-validate.wy-inline-validate-danger .wy-input-context,.wy-inline-validate.wy-inline-validate-warning .wy-input-context,.wy-inline-validate.wy-inline-validate-info .wy-input-context{font-family:inherit}.fa:before,.wy-menu-vertical li span.toctree-expand:before,.wy-menu-vertical li.on a span.toctree-expand:before,.wy-menu-vertical li.current>a span.toctree-expand:before,.rst-content .admonition-title:before,.rst-content h1 .headerlink:before,.rst-content h2 .headerlink:before,.rst-content h3 .headerlink:before,.rst-content h4 .headerlink:before,.rst-content h5 .headerlink:before,.rst-content h6 .headerlink:before,.rst-content dl dt .headerlink:before,.rst-content p.caption .headerlink:before,.rst-content table>caption .headerlink:before,.rst-content tt.download span:first-child:before,.rst-content code.download span:first-child:before,.icon:before,.wy-dropdown .caret:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before{font-family:"FontAwesome";display:inline-block;font-style:normal;font-weight:normal;line-height:1;text-decoration:inherit}a .fa,a .wy-menu-vertical li span.toctree-expand,.wy-menu-vertical li a span.toctree-expand,.wy-menu-vertical li.on a span.toctree-expand,.wy-menu-vertical li.current>a span.toctree-expand,a .rst-content .admonition-title,.rst-content a .admonition-title,a .rst-content h1 .headerlink,.rst-content h1 a .headerlink,a .rst-content h2 .headerlink,.rst-content h2 a .headerlink,a .rst-content h3 .headerlink,.rst-content h3 a .headerlink,a .rst-content h4 .headerlink,.rst-content h4 a .headerlink,a .rst-content h5 .headerlink,.rst-content h5 a .headerlink,a .rst-content h6 .headerlink,.rst-content h6 a .headerlink,a .rst-content dl dt .headerlink,.rst-content dl dt a .headerlink,a .rst-content p.caption .headerlink,.rst-content p.caption a .headerlink,a .rst-content table>caption .headerlink,.rst-content table>caption a .headerlink,a .rst-content tt.download span:first-child,.rst-content tt.download a span:first-child,a .rst-content code.download span:first-child,.rst-content code.download a span:first-child,a .icon{display:inline-block;text-decoration:inherit}.btn .fa,.btn .wy-menu-vertical li span.toctree-expand,.wy-menu-vertical li .btn span.toctree-expand,.btn .wy-menu-vertical li.on a span.toctree-expand,.wy-menu-vertical li.on a .btn span.toctree-expand,.btn .wy-menu-vertical li.current>a span.toctree-expand,.wy-menu-vertical li.current>a .btn span.toctree-expand,.btn .rst-content .admonition-title,.rst-content .btn .admonition-title,.btn .rst-content h1 .headerlink,.rst-content h1 .btn .headerlink,.btn .rst-content h2 .headerlink,.rst-content h2 .btn .headerlink,.btn .rst-content h3 .headerlink,.rst-content h3 .btn .headerlink,.btn .rst-content h4 .headerlink,.rst-content h4 .btn .headerlink,.btn .rst-content h5 .headerlink,.rst-content h5 .btn .headerlink,.btn .rst-content h6 .headerlink,.rst-content h6 .btn .headerlink,.btn .rst-content dl dt .headerlink,.rst-content dl dt .btn .headerlink,.btn .rst-content p.caption .headerlink,.rst-content p.caption .btn .headerlink,.btn .rst-content table>caption .headerlink,.rst-content table>caption .btn .headerlink,.btn .rst-content tt.download span:first-child,.rst-content tt.download .btn span:first-child,.btn .rst-content code.download span:first-child,.rst-content code.download .btn span:first-child,.btn .icon,.nav .fa,.nav .wy-menu-vertical li span.toctree-expand,.wy-menu-vertical li .nav span.toctree-expand,.nav .wy-menu-vertical li.on a span.toctree-expand,.wy-menu-vertical li.on a .nav span.toctree-expand,.nav .wy-menu-vertical li.current>a span.toctree-expand,.wy-menu-vertical li.current>a .nav span.toctree-expand,.nav .rst-content .admonition-title,.rst-content .nav .admonition-title,.nav .rst-content h1 .headerlink,.rst-content h1 .nav .headerlink,.nav .rst-content h2 .headerlink,.rst-content h2 .nav .headerlink,.nav .rst-content h3 .headerlink,.rst-content h3 .nav .headerlink,.nav .rst-content h4 .headerlink,.rst-content h4 .nav .headerlink,.nav .rst-content h5 .headerlink,.rst-content h5 .nav .headerlink,.nav .rst-content h6 .headerlink,.rst-content h6 .nav .headerlink,.nav .rst-content dl dt .headerlink,.rst-content dl dt .nav .headerlink,.nav .rst-content p.caption .headerlink,.rst-content p.caption .nav .headerlink,.nav .rst-content table>caption .headerlink,.rst-content table>caption .nav .headerlink,.nav .rst-content tt.download span:first-child,.rst-content tt.download .nav span:first-child,.nav .rst-content code.download span:first-child,.rst-content code.download .nav span:first-child,.nav .icon{display:inline}.btn .fa.fa-large,.btn .wy-menu-vertical li span.fa-large.toctree-expand,.wy-menu-vertical li .btn span.fa-large.toctree-expand,.btn .rst-content .fa-large.admonition-title,.rst-content .btn .fa-large.admonition-title,.btn .rst-content h1 .fa-large.headerlink,.rst-content h1 .btn .fa-large.headerlink,.btn .rst-content h2 .fa-large.headerlink,.rst-content h2 .btn .fa-large.headerlink,.btn .rst-content h3 .fa-large.headerlink,.rst-content h3 .btn .fa-large.headerlink,.btn .rst-content h4 .fa-large.headerlink,.rst-content h4 .btn .fa-large.headerlink,.btn .rst-content h5 .fa-large.headerlink,.rst-content h5 .btn .fa-large.headerlink,.btn .rst-content h6 .fa-large.headerlink,.rst-content h6 .btn .fa-large.headerlink,.btn .rst-content dl dt .fa-large.headerlink,.rst-content dl dt .btn .fa-large.headerlink,.btn .rst-content p.caption .fa-large.headerlink,.rst-content p.caption .btn .fa-large.headerlink,.btn .rst-content table>caption .fa-large.headerlink,.rst-content table>caption .btn .fa-large.headerlink,.btn .rst-content tt.download span.fa-large:first-child,.rst-content tt.download .btn span.fa-large:first-child,.btn .rst-content code.download span.fa-large:first-child,.rst-content code.download .btn span.fa-large:first-child,.btn .fa-large.icon,.nav .fa.fa-large,.nav .wy-menu-vertical li span.fa-large.toctree-expand,.wy-menu-vertical li .nav span.fa-large.toctree-expand,.nav .rst-content .fa-large.admonition-title,.rst-content .nav .fa-large.admonition-title,.nav .rst-content h1 .fa-large.headerlink,.rst-content h1 .nav .fa-large.headerlink,.nav .rst-content h2 .fa-large.headerlink,.rst-content h2 .nav .fa-large.headerlink,.nav .rst-content h3 .fa-large.headerlink,.rst-content h3 .nav .fa-large.headerlink,.nav .rst-content h4 .fa-large.headerlink,.rst-content h4 .nav .fa-large.headerlink,.nav .rst-content h5 .fa-large.headerlink,.rst-content h5 .nav .fa-large.headerlink,.nav .rst-content h6 .fa-large.headerlink,.rst-content h6 .nav .fa-large.headerlink,.nav .rst-content dl dt .fa-large.headerlink,.rst-content dl dt .nav .fa-large.headerlink,.nav .rst-content p.caption .fa-large.headerlink,.rst-content p.caption .nav .fa-large.headerlink,.nav .rst-content table>caption .fa-large.headerlink,.rst-content table>caption .nav .fa-large.headerlink,.nav .rst-content tt.download span.fa-large:first-child,.rst-content tt.download .nav span.fa-large:first-child,.nav .rst-content code.download span.fa-large:first-child,.rst-content code.download .nav span.fa-large:first-child,.nav .fa-large.icon{line-height:.9em}.btn .fa.fa-spin,.btn .wy-menu-vertical li span.fa-spin.toctree-expand,.wy-menu-vertical li .btn span.fa-spin.toctree-expand,.btn .rst-content .fa-spin.admonition-title,.rst-content .btn .fa-spin.admonition-title,.btn .rst-content h1 .fa-spin.headerlink,.rst-content h1 .btn .fa-spin.headerlink,.btn .rst-content h2 .fa-spin.headerlink,.rst-content h2 .btn .fa-spin.headerlink,.btn .rst-content h3 .fa-spin.headerlink,.rst-content h3 .btn .fa-spin.headerlink,.btn .rst-content h4 .fa-spin.headerlink,.rst-content h4 .btn .fa-spin.headerlink,.btn .rst-content h5 .fa-spin.headerlink,.rst-content h5 .btn .fa-spin.headerlink,.btn .rst-content h6 .fa-spin.headerlink,.rst-content h6 .btn .fa-spin.headerlink,.btn .rst-content dl dt .fa-spin.headerlink,.rst-content dl dt .btn .fa-spin.headerlink,.btn .rst-content p.caption .fa-spin.headerlink,.rst-content p.caption .btn .fa-spin.headerlink,.btn .rst-content table>caption .fa-spin.headerlink,.rst-content table>caption .btn .fa-spin.headerlink,.btn .rst-content tt.download span.fa-spin:first-child,.rst-content tt.download .btn span.fa-spin:first-child,.btn .rst-content code.download span.fa-spin:first-child,.rst-content code.download .btn span.fa-spin:first-child,.btn .fa-spin.icon,.nav .fa.fa-spin,.nav .wy-menu-vertical li span.fa-spin.toctree-expand,.wy-menu-vertical li .nav span.fa-spin.toctree-expand,.nav .rst-content .fa-spin.admonition-title,.rst-content .nav .fa-spin.admonition-title,.nav .rst-content h1 .fa-spin.headerlink,.rst-content h1 .nav .fa-spin.headerlink,.nav .rst-content h2 .fa-spin.headerlink,.rst-content h2 .nav .fa-spin.headerlink,.nav .rst-content h3 .fa-spin.headerlink,.rst-content h3 .nav .fa-spin.headerlink,.nav .rst-content h4 .fa-spin.headerlink,.rst-content h4 .nav .fa-spin.headerlink,.nav .rst-content h5 .fa-spin.headerlink,.rst-content h5 .nav .fa-spin.headerlink,.nav .rst-content h6 .fa-spin.headerlink,.rst-content h6 .nav .fa-spin.headerlink,.nav .rst-content dl dt .fa-spin.headerlink,.rst-content dl dt .nav .fa-spin.headerlink,.nav .rst-content p.caption .fa-spin.headerlink,.rst-content p.caption .nav .fa-spin.headerlink,.nav .rst-content table>caption .fa-spin.headerlink,.rst-content table>caption .nav .fa-spin.headerlink,.nav .rst-content tt.download span.fa-spin:first-child,.rst-content tt.download .nav span.fa-spin:first-child,.nav .rst-content code.download span.fa-spin:first-child,.rst-content code.download .nav span.fa-spin:first-child,.nav .fa-spin.icon{display:inline-block}.btn.fa:before,.wy-menu-vertical li span.btn.toctree-expand:before,.rst-content .btn.admonition-title:before,.rst-content h1 .btn.headerlink:before,.rst-content h2 .btn.headerlink:before,.rst-content h3 .btn.headerlink:before,.rst-content h4 .btn.headerlink:before,.rst-content h5 .btn.headerlink:before,.rst-content h6 .btn.headerlink:before,.rst-content dl dt .btn.headerlink:before,.rst-content p.caption .btn.headerlink:before,.rst-content table>caption .btn.headerlink:before,.rst-content tt.download span.btn:first-child:before,.rst-content code.download span.btn:first-child:before,.btn.icon:before{opacity:.5;-webkit-transition:opacity .05s ease-in;-moz-transition:opacity .05s ease-in;transition:opacity .05s ease-in}.btn.fa:hover:before,.wy-menu-vertical li span.btn.toctree-expand:hover:before,.rst-content .btn.admonition-title:hover:before,.rst-content h1 .btn.headerlink:hover:before,.rst-content h2 .btn.headerlink:hover:before,.rst-content h3 .btn.headerlink:hover:before,.rst-content h4 .btn.headerlink:hover:before,.rst-content h5 .btn.headerlink:hover:before,.rst-content h6 .btn.headerlink:hover:before,.rst-content dl dt .btn.headerlink:hover:before,.rst-content p.caption .btn.headerlink:hover:before,.rst-content table>caption .btn.headerlink:hover:before,.rst-content tt.download span.btn:first-child:hover:before,.rst-content code.download span.btn:first-child:hover:before,.btn.icon:hover:before{opacity:1}.btn-mini .fa:before,.btn-mini .wy-menu-vertical li span.toctree-expand:before,.wy-menu-vertical li .btn-mini span.toctree-expand:before,.btn-mini .rst-content .admonition-title:before,.rst-content .btn-mini .admonition-title:before,.btn-mini .rst-content h1 .headerlink:before,.rst-content h1 .btn-mini .headerlink:before,.btn-mini .rst-content h2 .headerlink:before,.rst-content h2 .btn-mini .headerlink:before,.btn-mini .rst-content h3 .headerlink:before,.rst-content h3 .btn-mini .headerlink:before,.btn-mini .rst-content h4 .headerlink:before,.rst-content h4 .btn-mini .headerlink:before,.btn-mini .rst-content h5 .headerlink:before,.rst-content h5 .btn-mini .headerlink:before,.btn-mini .rst-content h6 .headerlink:before,.rst-content h6 .btn-mini .headerlink:before,.btn-mini .rst-content dl dt .headerlink:before,.rst-content dl dt .btn-mini .headerlink:before,.btn-mini .rst-content p.caption .headerlink:before,.rst-content p.caption .btn-mini .headerlink:before,.btn-mini .rst-content table>caption .headerlink:before,.rst-content table>caption .btn-mini .headerlink:before,.btn-mini .rst-content tt.download span:first-child:before,.rst-content tt.download .btn-mini span:first-child:before,.btn-mini .rst-content code.download span:first-child:before,.rst-content code.download .btn-mini span:first-child:before,.btn-mini .icon:before{font-size:14px;vertical-align:-15%}.wy-alert,.rst-content .note,.rst-content .attention,.rst-content .caution,.rst-content .danger,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .tip,.rst-content .warning,.rst-content .seealso,.rst-content .admonition-todo,.rst-content .admonition{padding:12px;line-height:24px;margin-bottom:24px;background:#e7f2fa}.wy-alert-title,.rst-content .admonition-title{color:#fff;font-weight:bold;display:block;color:#fff;background:#6ab0de;margin:-12px;padding:6px 12px;margin-bottom:12px}.wy-alert.wy-alert-danger,.rst-content .wy-alert-danger.note,.rst-content .wy-alert-danger.attention,.rst-content .wy-alert-danger.caution,.rst-content .danger,.rst-content .error,.rst-content .wy-alert-danger.hint,.rst-content .wy-alert-danger.important,.rst-content .wy-alert-danger.tip,.rst-content .wy-alert-danger.warning,.rst-content .wy-alert-danger.seealso,.rst-content .wy-alert-danger.admonition-todo,.rst-content .wy-alert-danger.admonition{background:#fdf3f2}.wy-alert.wy-alert-danger .wy-alert-title,.rst-content .wy-alert-danger.note .wy-alert-title,.rst-content .wy-alert-danger.attention .wy-alert-title,.rst-content .wy-alert-danger.caution .wy-alert-title,.rst-content .danger .wy-alert-title,.rst-content .error .wy-alert-title,.rst-content .wy-alert-danger.hint .wy-alert-title,.rst-content .wy-alert-danger.important .wy-alert-title,.rst-content .wy-alert-danger.tip .wy-alert-title,.rst-content .wy-alert-danger.warning .wy-alert-title,.rst-content .wy-alert-danger.seealso .wy-alert-title,.rst-content .wy-alert-danger.admonition-todo .wy-alert-title,.rst-content .wy-alert-danger.admonition .wy-alert-title,.wy-alert.wy-alert-danger .rst-content .admonition-title,.rst-content .wy-alert.wy-alert-danger .admonition-title,.rst-content .wy-alert-danger.note .admonition-title,.rst-content .wy-alert-danger.attention .admonition-title,.rst-content .wy-alert-danger.caution .admonition-title,.rst-content .danger .admonition-title,.rst-content .error .admonition-title,.rst-content .wy-alert-danger.hint .admonition-title,.rst-content .wy-alert-danger.important .admonition-title,.rst-content .wy-alert-danger.tip .admonition-title,.rst-content .wy-alert-danger.warning .admonition-title,.rst-content .wy-alert-danger.seealso .admonition-title,.rst-content .wy-alert-danger.admonition-todo .admonition-title,.rst-content .wy-alert-danger.admonition .admonition-title{background:#f29f97}.wy-alert.wy-alert-warning,.rst-content .wy-alert-warning.note,.rst-content .attention,.rst-content .caution,.rst-content .wy-alert-warning.danger,.rst-content .wy-alert-warning.error,.rst-content .wy-alert-warning.hint,.rst-content .wy-alert-warning.important,.rst-content .wy-alert-warning.tip,.rst-content .warning,.rst-content .wy-alert-warning.seealso,.rst-content .admonition-todo,.rst-content .wy-alert-warning.admonition{background:#ffedcc}.wy-alert.wy-alert-warning .wy-alert-title,.rst-content .wy-alert-warning.note .wy-alert-title,.rst-content .attention .wy-alert-title,.rst-content .caution .wy-alert-title,.rst-content .wy-alert-warning.danger .wy-alert-title,.rst-content .wy-alert-warning.error .wy-alert-title,.rst-content .wy-alert-warning.hint .wy-alert-title,.rst-content .wy-alert-warning.important .wy-alert-title,.rst-content .wy-alert-warning.tip .wy-alert-title,.rst-content .warning .wy-alert-title,.rst-content .wy-alert-warning.seealso .wy-alert-title,.rst-content .admonition-todo .wy-alert-title,.rst-content .wy-alert-warning.admonition .wy-alert-title,.wy-alert.wy-alert-warning .rst-content .admonition-title,.rst-content .wy-alert.wy-alert-warning .admonition-title,.rst-content .wy-alert-warning.note .admonition-title,.rst-content .attention .admonition-title,.rst-content .caution .admonition-title,.rst-content .wy-alert-warning.danger .admonition-title,.rst-content .wy-alert-warning.error .admonition-title,.rst-content .wy-alert-warning.hint .admonition-title,.rst-content .wy-alert-warning.important .admonition-title,.rst-content .wy-alert-warning.tip .admonition-title,.rst-content .warning .admonition-title,.rst-content .wy-alert-warning.seealso .admonition-title,.rst-content .admonition-todo .admonition-title,.rst-content .wy-alert-warning.admonition .admonition-title{background:#f0b37e}.wy-alert.wy-alert-info,.rst-content .note,.rst-content .wy-alert-info.attention,.rst-content .wy-alert-info.caution,.rst-content .wy-alert-info.danger,.rst-content .wy-alert-info.error,.rst-content .wy-alert-info.hint,.rst-content .wy-alert-info.important,.rst-content .wy-alert-info.tip,.rst-content .wy-alert-info.warning,.rst-content .seealso,.rst-content .wy-alert-info.admonition-todo,.rst-content .wy-alert-info.admonition{background:#e7f2fa}.wy-alert.wy-alert-info .wy-alert-title,.rst-content .note .wy-alert-title,.rst-content .wy-alert-info.attention .wy-alert-title,.rst-content .wy-alert-info.caution .wy-alert-title,.rst-content .wy-alert-info.danger .wy-alert-title,.rst-content .wy-alert-info.error .wy-alert-title,.rst-content .wy-alert-info.hint .wy-alert-title,.rst-content .wy-alert-info.important .wy-alert-title,.rst-content .wy-alert-info.tip .wy-alert-title,.rst-content .wy-alert-info.warning .wy-alert-title,.rst-content .seealso .wy-alert-title,.rst-content .wy-alert-info.admonition-todo .wy-alert-title,.rst-content .wy-alert-info.admonition .wy-alert-title,.wy-alert.wy-alert-info .rst-content .admonition-title,.rst-content .wy-alert.wy-alert-info .admonition-title,.rst-content .note .admonition-title,.rst-content .wy-alert-info.attention .admonition-title,.rst-content .wy-alert-info.caution .admonition-title,.rst-content .wy-alert-info.danger .admonition-title,.rst-content .wy-alert-info.error .admonition-title,.rst-content .wy-alert-info.hint .admonition-title,.rst-content .wy-alert-info.important .admonition-title,.rst-content .wy-alert-info.tip .admonition-title,.rst-content .wy-alert-info.warning .admonition-title,.rst-content .seealso .admonition-title,.rst-content .wy-alert-info.admonition-todo .admonition-title,.rst-content .wy-alert-info.admonition .admonition-title{background:#6ab0de}.wy-alert.wy-alert-success,.rst-content .wy-alert-success.note,.rst-content .wy-alert-success.attention,.rst-content .wy-alert-success.caution,.rst-content .wy-alert-success.danger,.rst-content .wy-alert-success.error,.rst-content .hint,.rst-content .important,.rst-content .tip,.rst-content .wy-alert-success.warning,.rst-content .wy-alert-success.seealso,.rst-content .wy-alert-success.admonition-todo,.rst-content .wy-alert-success.admonition{background:#dbfaf4}.wy-alert.wy-alert-success .wy-alert-title,.rst-content .wy-alert-success.note .wy-alert-title,.rst-content .wy-alert-success.attention .wy-alert-title,.rst-content .wy-alert-success.caution .wy-alert-title,.rst-content .wy-alert-success.danger .wy-alert-title,.rst-content .wy-alert-success.error .wy-alert-title,.rst-content .hint .wy-alert-title,.rst-content .important .wy-alert-title,.rst-content .tip .wy-alert-title,.rst-content .wy-alert-success.warning .wy-alert-title,.rst-content .wy-alert-success.seealso .wy-alert-title,.rst-content .wy-alert-success.admonition-todo .wy-alert-title,.rst-content .wy-alert-success.admonition .wy-alert-title,.wy-alert.wy-alert-success .rst-content .admonition-title,.rst-content .wy-alert.wy-alert-success .admonition-title,.rst-content .wy-alert-success.note .admonition-title,.rst-content .wy-alert-success.attention .admonition-title,.rst-content .wy-alert-success.caution .admonition-title,.rst-content .wy-alert-success.danger .admonition-title,.rst-content .wy-alert-success.error .admonition-title,.rst-content .hint .admonition-title,.rst-content .important .admonition-title,.rst-content .tip .admonition-title,.rst-content .wy-alert-success.warning .admonition-title,.rst-content .wy-alert-success.seealso .admonition-title,.rst-content .wy-alert-success.admonition-todo .admonition-title,.rst-content .wy-alert-success.admonition .admonition-title{background:#1abc9c}.wy-alert.wy-alert-neutral,.rst-content .wy-alert-neutral.note,.rst-content .wy-alert-neutral.attention,.rst-content .wy-alert-neutral.caution,.rst-content .wy-alert-neutral.danger,.rst-content .wy-alert-neutral.error,.rst-content .wy-alert-neutral.hint,.rst-content .wy-alert-neutral.important,.rst-content .wy-alert-neutral.tip,.rst-content .wy-alert-neutral.warning,.rst-content .wy-alert-neutral.seealso,.rst-content .wy-alert-neutral.admonition-todo,.rst-content .wy-alert-neutral.admonition{background:#f3f6f6}.wy-alert.wy-alert-neutral .wy-alert-title,.rst-content .wy-alert-neutral.note .wy-alert-title,.rst-content .wy-alert-neutral.attention .wy-alert-title,.rst-content .wy-alert-neutral.caution .wy-alert-title,.rst-content .wy-alert-neutral.danger .wy-alert-title,.rst-content .wy-alert-neutral.error .wy-alert-title,.rst-content .wy-alert-neutral.hint .wy-alert-title,.rst-content .wy-alert-neutral.important .wy-alert-title,.rst-content .wy-alert-neutral.tip .wy-alert-title,.rst-content .wy-alert-neutral.warning .wy-alert-title,.rst-content .wy-alert-neutral.seealso .wy-alert-title,.rst-content .wy-alert-neutral.admonition-todo .wy-alert-title,.rst-content .wy-alert-neutral.admonition .wy-alert-title,.wy-alert.wy-alert-neutral .rst-content .admonition-title,.rst-content .wy-alert.wy-alert-neutral .admonition-title,.rst-content .wy-alert-neutral.note .admonition-title,.rst-content .wy-alert-neutral.attention .admonition-title,.rst-content .wy-alert-neutral.caution .admonition-title,.rst-content .wy-alert-neutral.danger .admonition-title,.rst-content .wy-alert-neutral.error .admonition-title,.rst-content .wy-alert-neutral.hint .admonition-title,.rst-content .wy-alert-neutral.important .admonition-title,.rst-content .wy-alert-neutral.tip .admonition-title,.rst-content .wy-alert-neutral.warning .admonition-title,.rst-content .wy-alert-neutral.seealso .admonition-title,.rst-content .wy-alert-neutral.admonition-todo .admonition-title,.rst-content .wy-alert-neutral.admonition .admonition-title{color:#404040;background:#e1e4e5}.wy-alert.wy-alert-neutral a,.rst-content .wy-alert-neutral.note a,.rst-content .wy-alert-neutral.attention a,.rst-content .wy-alert-neutral.caution a,.rst-content .wy-alert-neutral.danger a,.rst-content .wy-alert-neutral.error a,.rst-content .wy-alert-neutral.hint a,.rst-content .wy-alert-neutral.important a,.rst-content .wy-alert-neutral.tip a,.rst-content .wy-alert-neutral.warning a,.rst-content .wy-alert-neutral.seealso a,.rst-content .wy-alert-neutral.admonition-todo a,.rst-content .wy-alert-neutral.admonition a{color:#2980B9}.wy-alert p:last-child,.rst-content .note p:last-child,.rst-content .attention p:last-child,.rst-content .caution p:last-child,.rst-content .danger p:last-child,.rst-content .error p:last-child,.rst-content .hint p:last-child,.rst-content .important p:last-child,.rst-content .tip p:last-child,.rst-content .warning p:last-child,.rst-content .seealso p:last-child,.rst-content .admonition-todo p:last-child,.rst-content .admonition p:last-child{margin-bottom:0}.wy-tray-container{position:fixed;bottom:0px;left:0;z-index:600}.wy-tray-container li{display:block;width:300px;background:transparent;color:#fff;text-align:center;box-shadow:0 5px 5px 0 rgba(0,0,0,0.1);padding:0 24px;min-width:20%;opacity:0;height:0;line-height:56px;overflow:hidden;-webkit-transition:all .3s ease-in;-moz-transition:all .3s ease-in;transition:all .3s ease-in}.wy-tray-container li.wy-tray-item-success{background:#27AE60}.wy-tray-container li.wy-tray-item-info{background:#2980B9}.wy-tray-container li.wy-tray-item-warning{background:#E67E22}.wy-tray-container li.wy-tray-item-danger{background:#E74C3C}.wy-tray-container li.on{opacity:1;height:56px}@media screen and (max-width: 768px){.wy-tray-container{bottom:auto;top:0;width:100%}.wy-tray-container li{width:100%}}button{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle;cursor:pointer;line-height:normal;-webkit-appearance:button;*overflow:visible}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}button[disabled]{cursor:default}.btn{display:inline-block;border-radius:2px;line-height:normal;white-space:nowrap;text-align:center;cursor:pointer;font-size:100%;padding:6px 12px 8px 12px;color:#fff;border:1px solid rgba(0,0,0,0.1);background-color:#27AE60;text-decoration:none;font-weight:normal;font-family:"Lato","proxima-nova","Helvetica Neue",Arial,sans-serif;box-shadow:0px 1px 2px -1px rgba(255,255,255,0.5) inset,0px -2px 0px 0px rgba(0,0,0,0.1) inset;outline-none:false;vertical-align:middle;*display:inline;zoom:1;-webkit-user-drag:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-transition:all .1s linear;-moz-transition:all .1s linear;transition:all .1s linear}.btn-hover{background:#2e8ece;color:#fff}.btn:hover{background:#2cc36b;color:#fff}.btn:focus{background:#2cc36b;outline:0}.btn:active{box-shadow:0px -1px 0px 0px rgba(0,0,0,0.05) inset,0px 2px 0px 0px rgba(0,0,0,0.1) inset;padding:8px 12px 6px 12px}.btn:visited{color:#fff}.btn:disabled{background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);filter:alpha(opacity=40);opacity:.4;cursor:not-allowed;box-shadow:none}.btn-disabled{background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);filter:alpha(opacity=40);opacity:.4;cursor:not-allowed;box-shadow:none}.btn-disabled:hover,.btn-disabled:focus,.btn-disabled:active{background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);filter:alpha(opacity=40);opacity:.4;cursor:not-allowed;box-shadow:none}.btn::-moz-focus-inner{padding:0;border:0}.btn-small{font-size:80%}.btn-info{background-color:#2980B9 !important}.btn-info:hover{background-color:#2e8ece !important}.btn-neutral{background-color:#f3f6f6 !important;color:#404040 !important}.btn-neutral:hover{background-color:#e5ebeb !important;color:#404040}.btn-neutral:visited{color:#404040 !important}.btn-success{background-color:#27AE60 !important}.btn-success:hover{background-color:#295 !important}.btn-danger{background-color:#E74C3C !important}.btn-danger:hover{background-color:#ea6153 !important}.btn-warning{background-color:#E67E22 !important}.btn-warning:hover{background-color:#e98b39 !important}.btn-invert{background-color:#222}.btn-invert:hover{background-color:#2f2f2f !important}.btn-link{background-color:transparent !important;color:#2980B9;box-shadow:none;border-color:transparent !important}.btn-link:hover{background-color:transparent !important;color:#409ad5 !important;box-shadow:none}.btn-link:active{background-color:transparent !important;color:#409ad5 !important;box-shadow:none}.btn-link:visited{color:#9B59B6}.wy-btn-group .btn,.wy-control .btn{vertical-align:middle}.wy-btn-group{margin-bottom:24px;*zoom:1}.wy-btn-group:before,.wy-btn-group:after{display:table;content:""}.wy-btn-group:after{clear:both}.wy-dropdown{position:relative;display:inline-block}.wy-dropdown-active .wy-dropdown-menu{display:block}.wy-dropdown-menu{position:absolute;left:0;display:none;float:left;top:100%;min-width:100%;background:#fcfcfc;z-index:100;border:solid 1px #cfd7dd;box-shadow:0 2px 2px 0 rgba(0,0,0,0.1);padding:12px}.wy-dropdown-menu>dd>a{display:block;clear:both;color:#404040;white-space:nowrap;font-size:90%;padding:0 12px;cursor:pointer}.wy-dropdown-menu>dd>a:hover{background:#2980B9;color:#fff}.wy-dropdown-menu>dd.divider{border-top:solid 1px #cfd7dd;margin:6px 0}.wy-dropdown-menu>dd.search{padding-bottom:12px}.wy-dropdown-menu>dd.search input[type="search"]{width:100%}.wy-dropdown-menu>dd.call-to-action{background:#e3e3e3;text-transform:uppercase;font-weight:500;font-size:80%}.wy-dropdown-menu>dd.call-to-action:hover{background:#e3e3e3}.wy-dropdown-menu>dd.call-to-action .btn{color:#fff}.wy-dropdown.wy-dropdown-up .wy-dropdown-menu{bottom:100%;top:auto;left:auto;right:0}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu{background:#fcfcfc;margin-top:2px}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu a{padding:6px 12px}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu a:hover{background:#2980B9;color:#fff}.wy-dropdown.wy-dropdown-left .wy-dropdown-menu{right:0;left:auto;text-align:right}.wy-dropdown-arrow:before{content:" ";border-bottom:5px solid #f5f5f5;border-left:5px solid transparent;border-right:5px solid transparent;position:absolute;display:block;top:-4px;left:50%;margin-left:-3px}.wy-dropdown-arrow.wy-dropdown-arrow-left:before{left:11px}.wy-form-stacked select{display:block}.wy-form-aligned input,.wy-form-aligned textarea,.wy-form-aligned select,.wy-form-aligned .wy-help-inline,.wy-form-aligned label{display:inline-block;*display:inline;*zoom:1;vertical-align:middle}.wy-form-aligned .wy-control-group>label{display:inline-block;vertical-align:middle;width:10em;margin:6px 12px 0 0;float:left}.wy-form-aligned .wy-control{float:left}.wy-form-aligned .wy-control label{display:block}.wy-form-aligned .wy-control select{margin-top:6px}fieldset{border:0;margin:0;padding:0}legend{display:block;width:100%;border:0;padding:0;white-space:normal;margin-bottom:24px;font-size:150%;*margin-left:-7px}label{display:block;margin:0 0 .3125em 0;color:#333;font-size:90%}input,select,textarea{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle}.wy-control-group{margin-bottom:24px;*zoom:1;max-width:68em;margin-left:auto;margin-right:auto;*zoom:1}.wy-control-group:before,.wy-control-group:after{display:table;content:""}.wy-control-group:after{clear:both}.wy-control-group:before,.wy-control-group:after{display:table;content:""}.wy-control-group:after{clear:both}.wy-control-group.wy-control-group-required>label:after{content:" *";color:#E74C3C}.wy-control-group .wy-form-full,.wy-control-group .wy-form-halves,.wy-control-group .wy-form-thirds{padding-bottom:12px}.wy-control-group .wy-form-full select,.wy-control-group .wy-form-halves select,.wy-control-group .wy-form-thirds select{width:100%}.wy-control-group .wy-form-full input[type="text"],.wy-control-group .wy-form-full input[type="password"],.wy-control-group .wy-form-full input[type="email"],.wy-control-group .wy-form-full input[type="url"],.wy-control-group .wy-form-full input[type="date"],.wy-control-group .wy-form-full input[type="month"],.wy-control-group .wy-form-full input[type="time"],.wy-control-group .wy-form-full input[type="datetime"],.wy-control-group .wy-form-full input[type="datetime-local"],.wy-control-group .wy-form-full input[type="week"],.wy-control-group .wy-form-full input[type="number"],.wy-control-group .wy-form-full input[type="search"],.wy-control-group .wy-form-full input[type="tel"],.wy-control-group .wy-form-full input[type="color"],.wy-control-group .wy-form-halves input[type="text"],.wy-control-group .wy-form-halves input[type="password"],.wy-control-group .wy-form-halves input[type="email"],.wy-control-group .wy-form-halves input[type="url"],.wy-control-group .wy-form-halves input[type="date"],.wy-control-group .wy-form-halves input[type="month"],.wy-control-group .wy-form-halves input[type="time"],.wy-control-group .wy-form-halves input[type="datetime"],.wy-control-group .wy-form-halves input[type="datetime-local"],.wy-control-group .wy-form-halves input[type="week"],.wy-control-group .wy-form-halves input[type="number"],.wy-control-group .wy-form-halves input[type="search"],.wy-control-group .wy-form-halves input[type="tel"],.wy-control-group .wy-form-halves input[type="color"],.wy-control-group .wy-form-thirds input[type="text"],.wy-control-group .wy-form-thirds input[type="password"],.wy-control-group .wy-form-thirds input[type="email"],.wy-control-group .wy-form-thirds input[type="url"],.wy-control-group .wy-form-thirds input[type="date"],.wy-control-group .wy-form-thirds input[type="month"],.wy-control-group .wy-form-thirds input[type="time"],.wy-control-group .wy-form-thirds input[type="datetime"],.wy-control-group .wy-form-thirds input[type="datetime-local"],.wy-control-group .wy-form-thirds input[type="week"],.wy-control-group .wy-form-thirds input[type="number"],.wy-control-group .wy-form-thirds input[type="search"],.wy-control-group .wy-form-thirds input[type="tel"],.wy-control-group .wy-form-thirds input[type="color"]{width:100%}.wy-control-group .wy-form-full{float:left;display:block;margin-right:2.3576515979%;width:100%;margin-right:0}.wy-control-group .wy-form-full:last-child{margin-right:0}.wy-control-group .wy-form-halves{float:left;display:block;margin-right:2.3576515979%;width:48.821174201%}.wy-control-group .wy-form-halves:last-child{margin-right:0}.wy-control-group .wy-form-halves:nth-of-type(2n){margin-right:0}.wy-control-group .wy-form-halves:nth-of-type(2n+1){clear:left}.wy-control-group .wy-form-thirds{float:left;display:block;margin-right:2.3576515979%;width:31.7615656014%}.wy-control-group .wy-form-thirds:last-child{margin-right:0}.wy-control-group .wy-form-thirds:nth-of-type(3n){margin-right:0}.wy-control-group .wy-form-thirds:nth-of-type(3n+1){clear:left}.wy-control-group.wy-control-group-no-input .wy-control{margin:6px 0 0 0;font-size:90%}.wy-control-no-input{display:inline-block;margin:6px 0 0 0;font-size:90%}.wy-control-group.fluid-input input[type="text"],.wy-control-group.fluid-input input[type="password"],.wy-control-group.fluid-input input[type="email"],.wy-control-group.fluid-input input[type="url"],.wy-control-group.fluid-input input[type="date"],.wy-control-group.fluid-input input[type="month"],.wy-control-group.fluid-input input[type="time"],.wy-control-group.fluid-input input[type="datetime"],.wy-control-group.fluid-input input[type="datetime-local"],.wy-control-group.fluid-input input[type="week"],.wy-control-group.fluid-input input[type="number"],.wy-control-group.fluid-input input[type="search"],.wy-control-group.fluid-input input[type="tel"],.wy-control-group.fluid-input input[type="color"]{width:100%}.wy-form-message-inline{display:inline-block;padding-left:.3em;color:#666;vertical-align:middle;font-size:90%}.wy-form-message{display:block;color:#999;font-size:70%;margin-top:.3125em;font-style:italic}.wy-form-message p{font-size:inherit;font-style:italic;margin-bottom:6px}.wy-form-message p:last-child{margin-bottom:0}input{line-height:normal}input[type="button"],input[type="reset"],input[type="submit"]{-webkit-appearance:button;cursor:pointer;font-family:"Lato","proxima-nova","Helvetica Neue",Arial,sans-serif;*overflow:visible}input[type="text"],input[type="password"],input[type="email"],input[type="url"],input[type="date"],input[type="month"],input[type="time"],input[type="datetime"],input[type="datetime-local"],input[type="week"],input[type="number"],input[type="search"],input[type="tel"],input[type="color"]{-webkit-appearance:none;padding:6px;display:inline-block;border:1px solid #ccc;font-size:80%;font-family:"Lato","proxima-nova","Helvetica Neue",Arial,sans-serif;box-shadow:inset 0 1px 3px #ddd;border-radius:0;-webkit-transition:border .3s linear;-moz-transition:border .3s linear;transition:border .3s linear}input[type="datetime-local"]{padding:.34375em .625em}input[disabled]{cursor:default}input[type="checkbox"],input[type="radio"]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;padding:0;margin-right:.3125em;*height:13px;*width:13px}input[type="search"]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}input[type="search"]::-webkit-search-cancel-button,input[type="search"]::-webkit-search-decoration{-webkit-appearance:none}input[type="text"]:focus,input[type="password"]:focus,input[type="email"]:focus,input[type="url"]:focus,input[type="date"]:focus,input[type="month"]:focus,input[type="time"]:focus,input[type="datetime"]:focus,input[type="datetime-local"]:focus,input[type="week"]:focus,input[type="number"]:focus,input[type="search"]:focus,input[type="tel"]:focus,input[type="color"]:focus{outline:0;outline:thin dotted \9;border-color:#333}input.no-focus:focus{border-color:#ccc !important}input[type="file"]:focus,input[type="radio"]:focus,input[type="checkbox"]:focus{outline:thin dotted #333;outline:1px auto #129FEA}input[type="text"][disabled],input[type="password"][disabled],input[type="email"][disabled],input[type="url"][disabled],input[type="date"][disabled],input[type="month"][disabled],input[type="time"][disabled],input[type="datetime"][disabled],input[type="datetime-local"][disabled],input[type="week"][disabled],input[type="number"][disabled],input[type="search"][disabled],input[type="tel"][disabled],input[type="color"][disabled]{cursor:not-allowed;background-color:#fafafa}input:focus:invalid,textarea:focus:invalid,select:focus:invalid{color:#E74C3C;border:1px solid #E74C3C}input:focus:invalid:focus,textarea:focus:invalid:focus,select:focus:invalid:focus{border-color:#E74C3C}input[type="file"]:focus:invalid:focus,input[type="radio"]:focus:invalid:focus,input[type="checkbox"]:focus:invalid:focus{outline-color:#E74C3C}input.wy-input-large{padding:12px;font-size:100%}textarea{overflow:auto;vertical-align:top;width:100%;font-family:"Lato","proxima-nova","Helvetica Neue",Arial,sans-serif}select,textarea{padding:.5em .625em;display:inline-block;border:1px solid #ccc;font-size:80%;box-shadow:inset 0 1px 3px #ddd;-webkit-transition:border .3s linear;-moz-transition:border .3s linear;transition:border .3s linear}select{border:1px solid #ccc;background-color:#fff}select[multiple]{height:auto}select:focus,textarea:focus{outline:0}select[disabled],textarea[disabled],input[readonly],select[readonly],textarea[readonly]{cursor:not-allowed;background-color:#fafafa}input[type="radio"][disabled],input[type="checkbox"][disabled]{cursor:not-allowed}.wy-checkbox,.wy-radio{margin:6px 0;color:#404040;display:block}.wy-checkbox input,.wy-radio input{vertical-align:baseline}.wy-form-message-inline{display:inline-block;*display:inline;*zoom:1;vertical-align:middle}.wy-input-prefix,.wy-input-suffix{white-space:nowrap;padding:6px}.wy-input-prefix .wy-input-context,.wy-input-suffix .wy-input-context{line-height:27px;padding:0 8px;display:inline-block;font-size:80%;background-color:#f3f6f6;border:solid 1px #ccc;color:#999}.wy-input-suffix .wy-input-context{border-left:0}.wy-input-prefix .wy-input-context{border-right:0}.wy-switch{position:relative;display:block;height:24px;margin-top:12px;cursor:pointer}.wy-switch:before{position:absolute;content:"";display:block;left:0;top:0;width:36px;height:12px;border-radius:4px;background:#ccc;-webkit-transition:all .2s ease-in-out;-moz-transition:all .2s ease-in-out;transition:all .2s ease-in-out}.wy-switch:after{position:absolute;content:"";display:block;width:18px;height:18px;border-radius:4px;background:#999;left:-3px;top:-3px;-webkit-transition:all .2s ease-in-out;-moz-transition:all .2s ease-in-out;transition:all .2s ease-in-out}.wy-switch span{position:absolute;left:48px;display:block;font-size:12px;color:#ccc;line-height:1}.wy-switch.active:before{background:#1e8449}.wy-switch.active:after{left:24px;background:#27AE60}.wy-switch.disabled{cursor:not-allowed;opacity:.8}.wy-control-group.wy-control-group-error .wy-form-message,.wy-control-group.wy-control-group-error>label{color:#E74C3C}.wy-control-group.wy-control-group-error input[type="text"],.wy-control-group.wy-control-group-error input[type="password"],.wy-control-group.wy-control-group-error input[type="email"],.wy-control-group.wy-control-group-error input[type="url"],.wy-control-group.wy-control-group-error input[type="date"],.wy-control-group.wy-control-group-error input[type="month"],.wy-control-group.wy-control-group-error input[type="time"],.wy-control-group.wy-control-group-error input[type="datetime"],.wy-control-group.wy-control-group-error input[type="datetime-local"],.wy-control-group.wy-control-group-error input[type="week"],.wy-control-group.wy-control-group-error input[type="number"],.wy-control-group.wy-control-group-error input[type="search"],.wy-control-group.wy-control-group-error input[type="tel"],.wy-control-group.wy-control-group-error input[type="color"]{border:solid 1px #E74C3C}.wy-control-group.wy-control-group-error textarea{border:solid 1px #E74C3C}.wy-inline-validate{white-space:nowrap}.wy-inline-validate .wy-input-context{padding:.5em .625em;display:inline-block;font-size:80%}.wy-inline-validate.wy-inline-validate-success .wy-input-context{color:#27AE60}.wy-inline-validate.wy-inline-validate-danger .wy-input-context{color:#E74C3C}.wy-inline-validate.wy-inline-validate-warning .wy-input-context{color:#E67E22}.wy-inline-validate.wy-inline-validate-info .wy-input-context{color:#2980B9}.rotate-90{-webkit-transform:rotate(90deg);-moz-transform:rotate(90deg);-ms-transform:rotate(90deg);-o-transform:rotate(90deg);transform:rotate(90deg)}.rotate-180{-webkit-transform:rotate(180deg);-moz-transform:rotate(180deg);-ms-transform:rotate(180deg);-o-transform:rotate(180deg);transform:rotate(180deg)}.rotate-270{-webkit-transform:rotate(270deg);-moz-transform:rotate(270deg);-ms-transform:rotate(270deg);-o-transform:rotate(270deg);transform:rotate(270deg)}.mirror{-webkit-transform:scaleX(-1);-moz-transform:scaleX(-1);-ms-transform:scaleX(-1);-o-transform:scaleX(-1);transform:scaleX(-1)}.mirror.rotate-90{-webkit-transform:scaleX(-1) rotate(90deg);-moz-transform:scaleX(-1) rotate(90deg);-ms-transform:scaleX(-1) rotate(90deg);-o-transform:scaleX(-1) rotate(90deg);transform:scaleX(-1) rotate(90deg)}.mirror.rotate-180{-webkit-transform:scaleX(-1) rotate(180deg);-moz-transform:scaleX(-1) rotate(180deg);-ms-transform:scaleX(-1) rotate(180deg);-o-transform:scaleX(-1) rotate(180deg);transform:scaleX(-1) rotate(180deg)}.mirror.rotate-270{-webkit-transform:scaleX(-1) rotate(270deg);-moz-transform:scaleX(-1) rotate(270deg);-ms-transform:scaleX(-1) rotate(270deg);-o-transform:scaleX(-1) rotate(270deg);transform:scaleX(-1) rotate(270deg)}@media only screen and (max-width: 480px){.wy-form button[type="submit"]{margin:.7em 0 0}.wy-form input[type="text"],.wy-form input[type="password"],.wy-form input[type="email"],.wy-form input[type="url"],.wy-form input[type="date"],.wy-form input[type="month"],.wy-form input[type="time"],.wy-form input[type="datetime"],.wy-form input[type="datetime-local"],.wy-form input[type="week"],.wy-form input[type="number"],.wy-form input[type="search"],.wy-form input[type="tel"],.wy-form input[type="color"]{margin-bottom:.3em;display:block}.wy-form label{margin-bottom:.3em;display:block}.wy-form input[type="password"],.wy-form input[type="email"],.wy-form input[type="url"],.wy-form input[type="date"],.wy-form input[type="month"],.wy-form input[type="time"],.wy-form input[type="datetime"],.wy-form input[type="datetime-local"],.wy-form input[type="week"],.wy-form input[type="number"],.wy-form input[type="search"],.wy-form input[type="tel"],.wy-form input[type="color"]{margin-bottom:0}.wy-form-aligned .wy-control-group label{margin-bottom:.3em;text-align:left;display:block;width:100%}.wy-form-aligned .wy-control{margin:1.5em 0 0 0}.wy-form .wy-help-inline,.wy-form-message-inline,.wy-form-message{display:block;font-size:80%;padding:6px 0}}@media screen and (max-width: 768px){.tablet-hide{display:none}}@media screen and (max-width: 480px){.mobile-hide{display:none}}.float-left{float:left}.float-right{float:right}.full-width{width:100%}.wy-table,.rst-content table.docutils,.rst-content table.field-list{border-collapse:collapse;border-spacing:0;empty-cells:show;margin-bottom:24px}.wy-table caption,.rst-content table.docutils caption,.rst-content table.field-list caption{color:#000;font:italic 85%/1 arial,sans-serif;padding:1em 0;text-align:center}.wy-table td,.rst-content table.docutils td,.rst-content table.field-list td,.wy-table th,.rst-content table.docutils th,.rst-content table.field-list th{font-size:90%;margin:0;overflow:visible;padding:8px 16px}.wy-table td:first-child,.rst-content table.docutils td:first-child,.rst-content table.field-list td:first-child,.wy-table th:first-child,.rst-content table.docutils th:first-child,.rst-content table.field-list th:first-child{border-left-width:0}.wy-table thead,.rst-content table.docutils thead,.rst-content table.field-list thead{color:#000;text-align:left;vertical-align:bottom;white-space:nowrap}.wy-table thead th,.rst-content table.docutils thead th,.rst-content table.field-list thead th{font-weight:bold;border-bottom:solid 2px #e1e4e5}.wy-table td,.rst-content table.docutils td,.rst-content table.field-list td{background-color:transparent;vertical-align:middle}.wy-table td p,.rst-content table.docutils td p,.rst-content table.field-list td p{line-height:18px}.wy-table td p:last-child,.rst-content table.docutils td p:last-child,.rst-content table.field-list td p:last-child{margin-bottom:0}.wy-table .wy-table-cell-min,.rst-content table.docutils .wy-table-cell-min,.rst-content table.field-list .wy-table-cell-min{width:1%;padding-right:0}.wy-table .wy-table-cell-min input[type=checkbox],.rst-content table.docutils .wy-table-cell-min input[type=checkbox],.rst-content table.field-list .wy-table-cell-min input[type=checkbox],.wy-table .wy-table-cell-min input[type=checkbox],.rst-content table.docutils .wy-table-cell-min input[type=checkbox],.rst-content table.field-list .wy-table-cell-min input[type=checkbox]{margin:0}.wy-table-secondary{color:gray;font-size:90%}.wy-table-tertiary{color:gray;font-size:80%}.wy-table-odd td,.wy-table-striped tr:nth-child(2n-1) td,.rst-content table.docutils:not(.field-list) tr:nth-child(2n-1) td{background-color:#f3f6f6}.wy-table-backed{background-color:#f3f6f6}.wy-table-bordered-all,.rst-content table.docutils{border:1px solid #e1e4e5}.wy-table-bordered-all td,.rst-content table.docutils td{border-bottom:1px solid #e1e4e5;border-left:1px solid #e1e4e5}.wy-table-bordered-all tbody>tr:last-child td,.rst-content table.docutils tbody>tr:last-child td{border-bottom-width:0}.wy-table-bordered{border:1px solid #e1e4e5}.wy-table-bordered-rows td{border-bottom:1px solid #e1e4e5}.wy-table-bordered-rows tbody>tr:last-child td{border-bottom-width:0}.wy-table-horizontal tbody>tr:last-child td{border-bottom-width:0}.wy-table-horizontal td,.wy-table-horizontal th{border-width:0 0 1px 0;border-bottom:1px solid #e1e4e5}.wy-table-horizontal tbody>tr:last-child td{border-bottom-width:0}.wy-table-responsive{margin-bottom:24px;max-width:100%;overflow:auto}.wy-table-responsive table{margin-bottom:0 !important}.wy-table-responsive table td,.wy-table-responsive table th{white-space:nowrap}a{color:#2980B9;text-decoration:none;cursor:pointer}a:hover{color:#3091d1}a:visited{color:#9B59B6}html{height:100%;overflow-x:hidden}body{font-family:"Lato","proxima-nova","Helvetica Neue",Arial,sans-serif;font-weight:normal;color:#404040;min-height:100%;overflow-x:hidden;background:#edf0f2}.wy-text-left{text-align:left}.wy-text-center{text-align:center}.wy-text-right{text-align:right}.wy-text-large{font-size:120%}.wy-text-normal{font-size:100%}.wy-text-small,small{font-size:80%}.wy-text-strike{text-decoration:line-through}.wy-text-warning{color:#E67E22 !important}a.wy-text-warning:hover{color:#eb9950 !important}.wy-text-info{color:#2980B9 !important}a.wy-text-info:hover{color:#409ad5 !important}.wy-text-success{color:#27AE60 !important}a.wy-text-success:hover{color:#36d278 !important}.wy-text-danger{color:#E74C3C !important}a.wy-text-danger:hover{color:#ed7669 !important}.wy-text-neutral{color:#404040 !important}a.wy-text-neutral:hover{color:#595959 !important}h1,h2,.rst-content .toctree-wrapper p.caption,h3,h4,h5,h6,legend{margin-top:0;font-weight:700;font-family:"Roboto Slab","ff-tisa-web-pro","Georgia",Arial,sans-serif}p{line-height:24px;margin:0;font-size:16px;margin-bottom:24px}h1{font-size:175%}h2,.rst-content .toctree-wrapper p.caption{font-size:150%}h3{font-size:125%}h4{font-size:115%}h5{font-size:110%}h6{font-size:100%}hr{display:block;height:1px;border:0;border-top:1px solid #e1e4e5;margin:24px 0;padding:0}code,.rst-content tt,.rst-content code{white-space:nowrap;max-width:100%;background:#fff;border:solid 1px #e1e4e5;font-size:75%;padding:0 5px;font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",Courier,monospace;color:#E74C3C;overflow-x:auto}code.code-large,.rst-content tt.code-large{font-size:90%}.wy-plain-list-disc,.rst-content .section ul,.rst-content .toctree-wrapper ul,article ul{list-style:disc;line-height:24px;margin-bottom:24px}.wy-plain-list-disc li,.rst-content .section ul li,.rst-content .toctree-wrapper ul li,article ul li{list-style:disc;margin-left:24px}.wy-plain-list-disc li p:last-child,.rst-content .section ul li p:last-child,.rst-content .toctree-wrapper ul li p:last-child,article ul li p:last-child{margin-bottom:0}.wy-plain-list-disc li ul,.rst-content .section ul li ul,.rst-content .toctree-wrapper ul li ul,article ul li ul{margin-bottom:0}.wy-plain-list-disc li li,.rst-content .section ul li li,.rst-content .toctree-wrapper ul li li,article ul li li{list-style:circle}.wy-plain-list-disc li li li,.rst-content .section ul li li li,.rst-content .toctree-wrapper ul li li li,article ul li li li{list-style:square}.wy-plain-list-disc li ol li,.rst-content .section ul li ol li,.rst-content .toctree-wrapper ul li ol li,article ul li ol li{list-style:decimal}.wy-plain-list-decimal,.rst-content .section ol,.rst-content ol.arabic,article ol{list-style:decimal;line-height:24px;margin-bottom:24px}.wy-plain-list-decimal li,.rst-content .section ol li,.rst-content ol.arabic li,article ol li{list-style:decimal;margin-left:24px}.wy-plain-list-decimal li p:last-child,.rst-content .section ol li p:last-child,.rst-content ol.arabic li p:last-child,article ol li p:last-child{margin-bottom:0}.wy-plain-list-decimal li ul,.rst-content .section ol li ul,.rst-content ol.arabic li ul,article ol li ul{margin-bottom:0}.wy-plain-list-decimal li ul li,.rst-content .section ol li ul li,.rst-content ol.arabic li ul li,article ol li ul li{list-style:disc}.wy-breadcrumbs{*zoom:1}.wy-breadcrumbs:before,.wy-breadcrumbs:after{display:table;content:""}.wy-breadcrumbs:after{clear:both}.wy-breadcrumbs li{display:inline-block}.wy-breadcrumbs li.wy-breadcrumbs-aside{float:right}.wy-breadcrumbs li a{display:inline-block;padding:5px}.wy-breadcrumbs li a:first-child{padding-left:0}.wy-breadcrumbs li code,.wy-breadcrumbs li .rst-content tt,.rst-content .wy-breadcrumbs li tt{padding:5px;border:none;background:none}.wy-breadcrumbs li code.literal,.wy-breadcrumbs li .rst-content tt.literal,.rst-content .wy-breadcrumbs li tt.literal{color:#404040}.wy-breadcrumbs-extra{margin-bottom:0;color:#b3b3b3;font-size:80%;display:inline-block}@media screen and (max-width: 480px){.wy-breadcrumbs-extra{display:none}.wy-breadcrumbs li.wy-breadcrumbs-aside{display:none}}@media print{.wy-breadcrumbs li.wy-breadcrumbs-aside{display:none}}.wy-affix{position:fixed;top:1.618em}.wy-menu a:hover{text-decoration:none}.wy-menu-horiz{*zoom:1}.wy-menu-horiz:before,.wy-menu-horiz:after{display:table;content:""}.wy-menu-horiz:after{clear:both}.wy-menu-horiz ul,.wy-menu-horiz li{display:inline-block}.wy-menu-horiz li:hover{background:rgba(255,255,255,0.1)}.wy-menu-horiz li.divide-left{border-left:solid 1px #404040}.wy-menu-horiz li.divide-right{border-right:solid 1px #404040}.wy-menu-horiz a{height:32px;display:inline-block;line-height:32px;padding:0 16px}.wy-menu-vertical{width:300px}.wy-menu-vertical header,.wy-menu-vertical p.caption{height:32px;display:inline-block;line-height:32px;padding:0 1.618em;margin-bottom:0;display:block;font-weight:bold;text-transform:uppercase;font-size:80%;white-space:nowrap}.wy-menu-vertical ul{margin-bottom:0}.wy-menu-vertical li.divide-top{border-top:solid 1px #404040}.wy-menu-vertical li.divide-bottom{border-bottom:solid 1px #404040}.wy-menu-vertical li.current{background:#e3e3e3}.wy-menu-vertical li.current a{color:gray;border-right:solid 1px #c9c9c9;padding:.4045em 2.427em}.wy-menu-vertical li.current a:hover{background:#d6d6d6}.wy-menu-vertical li code,.wy-menu-vertical li .rst-content tt,.rst-content .wy-menu-vertical li tt{border:none;background:inherit;color:inherit;padding-left:0;padding-right:0}.wy-menu-vertical li span.toctree-expand{display:block;float:left;margin-left:-1.2em;font-size:.8em;line-height:1.6em;color:#4d4d4d}.wy-menu-vertical li.on a,.wy-menu-vertical li.current>a{color:#404040;padding:.4045em 1.618em;font-weight:bold;position:relative;background:#fcfcfc;border:none;padding-left:1.618em -4px}.wy-menu-vertical li.on a:hover,.wy-menu-vertical li.current>a:hover{background:#fcfcfc}.wy-menu-vertical li.on a:hover span.toctree-expand,.wy-menu-vertical li.current>a:hover span.toctree-expand{color:gray}.wy-menu-vertical li.on a span.toctree-expand,.wy-menu-vertical li.current>a span.toctree-expand{display:block;font-size:.8em;line-height:1.6em;color:#333}.wy-menu-vertical li.toctree-l1.current>a{border-bottom:solid 1px #c9c9c9;border-top:solid 1px #c9c9c9}.wy-menu-vertical li.toctree-l2 a,.wy-menu-vertical li.toctree-l3 a,.wy-menu-vertical li.toctree-l4 a{color:#404040}.wy-menu-vertical li.toctree-l1.current li.toctree-l2>ul,.wy-menu-vertical li.toctree-l2.current li.toctree-l3>ul{display:none}.wy-menu-vertical li.toctree-l1.current li.toctree-l2.current>ul,.wy-menu-vertical li.toctree-l2.current li.toctree-l3.current>ul{display:block}.wy-menu-vertical li.toctree-l2.current>a{background:#c9c9c9;padding:.4045em 2.427em}.wy-menu-vertical li.toctree-l2.current li.toctree-l3>a{display:block;background:#c9c9c9;padding:.4045em 4.045em}.wy-menu-vertical li.toctree-l2 a:hover span.toctree-expand{color:gray}.wy-menu-vertical li.toctree-l2 span.toctree-expand{color:#a3a3a3}.wy-menu-vertical li.toctree-l3{font-size:.9em}.wy-menu-vertical li.toctree-l3.current>a{background:#bdbdbd;padding:.4045em 4.045em}.wy-menu-vertical li.toctree-l3.current li.toctree-l4>a{display:block;background:#bdbdbd;padding:.4045em 5.663em}.wy-menu-vertical li.toctree-l3 a:hover span.toctree-expand{color:gray}.wy-menu-vertical li.toctree-l3 span.toctree-expand{color:#969696}.wy-menu-vertical li.toctree-l4{font-size:.9em}.wy-menu-vertical li.current ul{display:block}.wy-menu-vertical li ul{margin-bottom:0;display:none}.wy-menu-vertical li ul li a{margin-bottom:0;color:#d9d9d9;font-weight:normal}.wy-menu-vertical a{display:inline-block;line-height:18px;padding:.4045em 1.618em;display:block;position:relative;font-size:90%;color:#d9d9d9}.wy-menu-vertical a:hover{background-color:#4e4a4a;cursor:pointer}.wy-menu-vertical a:hover span.toctree-expand{color:#d9d9d9}.wy-menu-vertical a:active{background-color:#2980B9;cursor:pointer;color:#fff}.wy-menu-vertical a:active span.toctree-expand{color:#fff}.wy-side-nav-search{display:block;width:300px;padding:.809em;margin-bottom:.809em;z-index:200;background-color:#2980B9;text-align:center;padding:.809em;display:block;color:#fcfcfc;margin-bottom:.809em}.wy-side-nav-search input[type=text]{width:100%;border-radius:50px;padding:6px 12px;border-color:#2472a4}.wy-side-nav-search img{display:block;margin:auto auto .809em auto;height:45px;width:45px;background-color:#2980B9;padding:5px;border-radius:100%}.wy-side-nav-search>a,.wy-side-nav-search .wy-dropdown>a{color:#fcfcfc;font-size:100%;font-weight:bold;display:inline-block;padding:4px 6px;margin-bottom:.809em}.wy-side-nav-search>a:hover,.wy-side-nav-search .wy-dropdown>a:hover{background:rgba(255,255,255,0.1)}.wy-side-nav-search>a img.logo,.wy-side-nav-search .wy-dropdown>a img.logo{display:block;margin:0 auto;height:auto;width:auto;border-radius:0;max-width:100%;background:transparent}.wy-side-nav-search>a.icon img.logo,.wy-side-nav-search .wy-dropdown>a.icon img.logo{margin-top:.85em}.wy-side-nav-search>div.version{margin-top:-.4045em;margin-bottom:.809em;font-weight:normal;color:rgba(255,255,255,0.3)}.wy-nav .wy-menu-vertical header{color:#2980B9}.wy-nav .wy-menu-vertical a{color:#b3b3b3}.wy-nav .wy-menu-vertical a:hover{background-color:#2980B9;color:#fff}[data-menu-wrap]{-webkit-transition:all .2s ease-in;-moz-transition:all .2s ease-in;transition:all .2s ease-in;position:absolute;opacity:1;width:100%;opacity:0}[data-menu-wrap].move-center{left:0;right:auto;opacity:1}[data-menu-wrap].move-left{right:auto;left:-100%;opacity:0}[data-menu-wrap].move-right{right:-100%;left:auto;opacity:0}.wy-body-for-nav{background:#fcfcfc}.wy-grid-for-nav{position:absolute;width:100%;height:100%}.wy-nav-side{position:fixed;top:0;bottom:0;left:0;padding-bottom:2em;width:300px;overflow-x:hidden;overflow-y:hidden;min-height:100%;color:#9b9b9b;background:#343131;z-index:200}.wy-side-scroll{width:320px;position:relative;overflow-x:hidden;overflow-y:scroll;height:100%}.wy-nav-top{display:none;background:#2980B9;color:#fff;padding:.4045em .809em;position:relative;line-height:50px;text-align:center;font-size:100%;*zoom:1}.wy-nav-top:before,.wy-nav-top:after{display:table;content:""}.wy-nav-top:after{clear:both}.wy-nav-top a{color:#fff;font-weight:bold}.wy-nav-top img{margin-right:12px;height:45px;width:45px;background-color:#2980B9;padding:5px;border-radius:100%}.wy-nav-top i{font-size:30px;float:left;cursor:pointer;padding-top:inherit}.wy-nav-content-wrap{margin-left:300px;background:#fcfcfc;min-height:100%}.wy-nav-content{padding:1.618em 3.236em;height:100%;max-width:800px;margin:auto}.wy-body-mask{position:fixed;width:100%;height:100%;background:rgba(0,0,0,0.2);display:none;z-index:499}.wy-body-mask.on{display:block}footer{color:gray}footer p{margin-bottom:12px}footer span.commit code,footer span.commit .rst-content tt,.rst-content footer span.commit tt{padding:0px;font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",Courier,monospace;font-size:1em;background:none;border:none;color:gray}.rst-footer-buttons{*zoom:1}.rst-footer-buttons:before,.rst-footer-buttons:after{width:100%}.rst-footer-buttons:before,.rst-footer-buttons:after{display:table;content:""}.rst-footer-buttons:after{clear:both}.rst-breadcrumbs-buttons{margin-top:12px;*zoom:1}.rst-breadcrumbs-buttons:before,.rst-breadcrumbs-buttons:after{display:table;content:""}.rst-breadcrumbs-buttons:after{clear:both}#search-results .search li{margin-bottom:24px;border-bottom:solid 1px #e1e4e5;padding-bottom:24px}#search-results .search li:first-child{border-top:solid 1px #e1e4e5;padding-top:24px}#search-results .search li a{font-size:120%;margin-bottom:12px;display:inline-block}#search-results .context{color:gray;font-size:90%}@media screen and (max-width: 768px){.wy-body-for-nav{background:#fcfcfc}.wy-nav-top{display:block}.wy-nav-side{left:-300px}.wy-nav-side.shift{width:85%;left:0}.wy-side-scroll{width:auto}.wy-side-nav-search{width:auto}.wy-menu.wy-menu-vertical{width:auto}.wy-nav-content-wrap{margin-left:0}.wy-nav-content-wrap .wy-nav-content{padding:1.618em}.wy-nav-content-wrap.shift{position:fixed;min-width:100%;left:85%;top:0;height:100%;overflow:hidden}}@media screen and (min-width: 1100px){.wy-nav-content-wrap{background:rgba(0,0,0,0.05)}.wy-nav-content{margin:0;background:#fcfcfc}}@media print{.rst-versions,footer,.wy-nav-side{display:none}.wy-nav-content-wrap{margin-left:0}}.rst-versions{position:fixed;bottom:0;left:0;width:300px;color:#fcfcfc;background:#1f1d1d;font-family:"Lato","proxima-nova","Helvetica Neue",Arial,sans-serif;z-index:400}.rst-versions a{color:#2980B9;text-decoration:none}.rst-versions .rst-badge-small{display:none}.rst-versions .rst-current-version{padding:12px;background-color:#272525;display:block;text-align:right;font-size:90%;cursor:pointer;color:#27AE60;*zoom:1}.rst-versions .rst-current-version:before,.rst-versions .rst-current-version:after{display:table;content:""}.rst-versions .rst-current-version:after{clear:both}.rst-versions .rst-current-version .fa,.rst-versions .rst-current-version .wy-menu-vertical li span.toctree-expand,.wy-menu-vertical li .rst-versions .rst-current-version span.toctree-expand,.rst-versions .rst-current-version .rst-content .admonition-title,.rst-content .rst-versions .rst-current-version .admonition-title,.rst-versions .rst-current-version .rst-content h1 .headerlink,.rst-content h1 .rst-versions .rst-current-version .headerlink,.rst-versions .rst-current-version .rst-content h2 .headerlink,.rst-content h2 .rst-versions .rst-current-version .headerlink,.rst-versions .rst-current-version .rst-content h3 .headerlink,.rst-content h3 .rst-versions .rst-current-version .headerlink,.rst-versions .rst-current-version .rst-content h4 .headerlink,.rst-content h4 .rst-versions .rst-current-version .headerlink,.rst-versions .rst-current-version .rst-content h5 .headerlink,.rst-content h5 .rst-versions .rst-current-version .headerlink,.rst-versions .rst-current-version .rst-content h6 .headerlink,.rst-content h6 .rst-versions .rst-current-version .headerlink,.rst-versions .rst-current-version .rst-content dl dt .headerlink,.rst-content dl dt .rst-versions .rst-current-version .headerlink,.rst-versions .rst-current-version .rst-content p.caption .headerlink,.rst-content p.caption .rst-versions .rst-current-version .headerlink,.rst-versions .rst-current-version .rst-content table>caption .headerlink,.rst-content table>caption .rst-versions .rst-current-version .headerlink,.rst-versions .rst-current-version .rst-content tt.download span:first-child,.rst-content tt.download .rst-versions .rst-current-version span:first-child,.rst-versions .rst-current-version .rst-content code.download span:first-child,.rst-content code.download .rst-versions .rst-current-version span:first-child,.rst-versions .rst-current-version .icon{color:#fcfcfc}.rst-versions .rst-current-version .fa-book,.rst-versions .rst-current-version .icon-book{float:left}.rst-versions .rst-current-version .icon-book{float:left}.rst-versions .rst-current-version.rst-out-of-date{background-color:#E74C3C;color:#fff}.rst-versions .rst-current-version.rst-active-old-version{background-color:#F1C40F;color:#000}.rst-versions.shift-up{height:auto;max-height:100%}.rst-versions.shift-up .rst-other-versions{display:block}.rst-versions .rst-other-versions{font-size:90%;padding:12px;color:gray;display:none}.rst-versions .rst-other-versions hr{display:block;height:1px;border:0;margin:20px 0;padding:0;border-top:solid 1px #413d3d}.rst-versions .rst-other-versions dd{display:inline-block;margin:0}.rst-versions .rst-other-versions dd a{display:inline-block;padding:6px;color:#fcfcfc}.rst-versions.rst-badge{width:auto;bottom:20px;right:20px;left:auto;border:none;max-width:300px}.rst-versions.rst-badge .icon-book{float:none}.rst-versions.rst-badge .fa-book,.rst-versions.rst-badge .icon-book{float:none}.rst-versions.rst-badge.shift-up .rst-current-version{text-align:right}.rst-versions.rst-badge.shift-up .rst-current-version .fa-book,.rst-versions.rst-badge.shift-up .rst-current-version .icon-book{float:left}.rst-versions.rst-badge.shift-up .rst-current-version .icon-book{float:left}.rst-versions.rst-badge .rst-current-version{width:auto;height:30px;line-height:30px;padding:0 6px;display:block;text-align:center}@media screen and (max-width: 768px){.rst-versions{width:85%;display:none}.rst-versions.shift{display:block}}.rst-content img{max-width:100%;height:auto}.rst-content div.figure{margin-bottom:24px}.rst-content div.figure p.caption{font-style:italic}.rst-content div.figure p:last-child.caption{margin-bottom:0px}.rst-content div.figure.align-center{text-align:center}.rst-content .section>img,.rst-content .section>a>img{margin-bottom:24px}.rst-content abbr[title]{text-decoration:none}.rst-content.style-external-links a.reference.external:after{font-family:FontAwesome;content:"";color:#b3b3b3;vertical-align:super;font-size:60%;margin:0 .2em}.rst-content blockquote{margin-left:24px;line-height:24px;margin-bottom:24px}.rst-content pre.literal-block{white-space:pre;margin:0;padding:12px 12px;font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",Courier,monospace;display:block;overflow:auto}.rst-content pre.literal-block,.rst-content div[class^='highlight']{border:1px solid #e1e4e5;overflow-x:auto;margin:1px 0 24px 0}.rst-content pre.literal-block div[class^='highlight'],.rst-content div[class^='highlight'] div[class^='highlight']{padding:0px;border:none;margin:0}.rst-content div[class^='highlight'] td.code{width:100%}.rst-content .linenodiv pre{border-right:solid 1px #e6e9ea;margin:0;padding:12px 12px;font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",Courier,monospace;user-select:none;pointer-events:none}.rst-content div[class^='highlight'] pre{white-space:pre;margin:0;padding:12px 12px;display:block;overflow:auto}.rst-content div[class^='highlight'] pre .hll{display:block;margin:0 -12px;padding:0 12px}.rst-content pre.literal-block,.rst-content div[class^='highlight'] pre,.rst-content .linenodiv pre{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",Courier,monospace;font-size:12px;line-height:1.4}@media print{.rst-content .codeblock,.rst-content div[class^='highlight'],.rst-content div[class^='highlight'] pre{white-space:pre-wrap}}.rst-content .note .last,.rst-content .attention .last,.rst-content .caution .last,.rst-content .danger .last,.rst-content .error .last,.rst-content .hint .last,.rst-content .important .last,.rst-content .tip .last,.rst-content .warning .last,.rst-content .seealso .last,.rst-content .admonition-todo .last,.rst-content .admonition .last{margin-bottom:0}.rst-content .admonition-title:before{margin-right:4px}.rst-content .admonition table{border-color:rgba(0,0,0,0.1)}.rst-content .admonition table td,.rst-content .admonition table th{background:transparent !important;border-color:rgba(0,0,0,0.1) !important}.rst-content .section ol.loweralpha,.rst-content .section ol.loweralpha li{list-style:lower-alpha}.rst-content .section ol.upperalpha,.rst-content .section ol.upperalpha li{list-style:upper-alpha}.rst-content .section ol p,.rst-content .section ul p{margin-bottom:12px}.rst-content .section ol p:last-child,.rst-content .section ul p:last-child{margin-bottom:24px}.rst-content .line-block{margin-left:0px;margin-bottom:24px;line-height:24px}.rst-content .line-block .line-block{margin-left:24px;margin-bottom:0px}.rst-content .topic-title{font-weight:bold;margin-bottom:12px}.rst-content .toc-backref{color:#404040}.rst-content .align-right{float:right;margin:0px 0px 24px 24px}.rst-content .align-left{float:left;margin:0px 24px 24px 0px}.rst-content .align-center{margin:auto}.rst-content .align-center:not(table){display:block}.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content .toctree-wrapper p.caption .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content dl dt .headerlink,.rst-content p.caption .headerlink,.rst-content table>caption .headerlink{visibility:hidden;font-size:14px}.rst-content h1 .headerlink:after,.rst-content h2 .headerlink:after,.rst-content .toctree-wrapper p.caption .headerlink:after,.rst-content h3 .headerlink:after,.rst-content h4 .headerlink:after,.rst-content h5 .headerlink:after,.rst-content h6 .headerlink:after,.rst-content dl dt .headerlink:after,.rst-content p.caption .headerlink:after,.rst-content table>caption .headerlink:after{content:"";font-family:FontAwesome}.rst-content h1:hover .headerlink:after,.rst-content h2:hover .headerlink:after,.rst-content .toctree-wrapper p.caption:hover .headerlink:after,.rst-content h3:hover .headerlink:after,.rst-content h4:hover .headerlink:after,.rst-content h5:hover .headerlink:after,.rst-content h6:hover .headerlink:after,.rst-content dl dt:hover .headerlink:after,.rst-content p.caption:hover .headerlink:after,.rst-content table>caption:hover .headerlink:after{visibility:visible}.rst-content table>caption .headerlink:after{font-size:12px}.rst-content .centered{text-align:center}.rst-content .sidebar{float:right;width:40%;display:block;margin:0 0 24px 24px;padding:24px;background:#f3f6f6;border:solid 1px #e1e4e5}.rst-content .sidebar p,.rst-content .sidebar ul,.rst-content .sidebar dl{font-size:90%}.rst-content .sidebar .last{margin-bottom:0}.rst-content .sidebar .sidebar-title{display:block;font-family:"Roboto Slab","ff-tisa-web-pro","Georgia",Arial,sans-serif;font-weight:bold;background:#e1e4e5;padding:6px 12px;margin:-24px;margin-bottom:24px;font-size:100%}.rst-content .highlighted{background:#F1C40F;display:inline-block;font-weight:bold;padding:0 6px}.rst-content .footnote-reference,.rst-content .citation-reference{vertical-align:baseline;position:relative;top:-0.4em;line-height:0;font-size:90%}.rst-content table.docutils.citation,.rst-content table.docutils.footnote{background:none;border:none;color:gray}.rst-content table.docutils.citation td,.rst-content table.docutils.citation tr,.rst-content table.docutils.footnote td,.rst-content table.docutils.footnote tr{border:none;background-color:transparent !important;white-space:normal}.rst-content table.docutils.citation td.label,.rst-content table.docutils.footnote td.label{padding-left:0;padding-right:0;vertical-align:top}.rst-content table.docutils.citation tt,.rst-content table.docutils.citation code,.rst-content table.docutils.footnote tt,.rst-content table.docutils.footnote code{color:#555}.rst-content .wy-table-responsive.citation,.rst-content .wy-table-responsive.footnote{margin-bottom:0}.rst-content .wy-table-responsive.citation+:not(.citation),.rst-content .wy-table-responsive.footnote+:not(.footnote){margin-top:24px}.rst-content .wy-table-responsive.citation:last-child,.rst-content .wy-table-responsive.footnote:last-child{margin-bottom:24px}.rst-content table.docutils th{border-color:#e1e4e5}.rst-content table.docutils td .last,.rst-content table.docutils td .last :last-child{margin-bottom:0}.rst-content table.field-list{border:none}.rst-content table.field-list td{border:none}.rst-content table.field-list td>strong{display:inline-block}.rst-content table.field-list .field-name{padding-right:10px;text-align:left;white-space:nowrap}.rst-content table.field-list .field-body{text-align:left}.rst-content tt,.rst-content tt,.rst-content code{color:#000;font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",Courier,monospace;padding:2px 5px}.rst-content tt big,.rst-content tt em,.rst-content tt big,.rst-content code big,.rst-content tt em,.rst-content code em{font-size:100% !important;line-height:normal}.rst-content tt.literal,.rst-content tt.literal,.rst-content code.literal{color:#E74C3C}.rst-content tt.xref,a .rst-content tt,.rst-content tt.xref,.rst-content code.xref,a .rst-content tt,a .rst-content code{font-weight:bold;color:#404040}.rst-content pre,.rst-content kbd,.rst-content samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",Courier,monospace}.rst-content a tt,.rst-content a tt,.rst-content a code{color:#2980B9}.rst-content dl{margin-bottom:24px}.rst-content dl dt{font-weight:bold;margin-bottom:12px}.rst-content dl p,.rst-content dl table,.rst-content dl ul,.rst-content dl ol{margin-bottom:12px !important}.rst-content dl dd{margin:0 0 12px 24px;line-height:24px}.rst-content dl:not(.docutils){margin-bottom:24px}.rst-content dl:not(.docutils) dt{display:table;margin:6px 0;font-size:90%;line-height:normal;background:#e7f2fa;color:#2980B9;border-top:solid 3px #6ab0de;padding:6px;position:relative}.rst-content dl:not(.docutils) dt:before{color:#6ab0de}.rst-content dl:not(.docutils) dt .headerlink{color:#404040;font-size:100% !important}.rst-content dl:not(.docutils) dl dt{margin-bottom:6px;border:none;border-left:solid 3px #ccc;background:#f0f0f0;color:#555}.rst-content dl:not(.docutils) dl dt .headerlink{color:#404040;font-size:100% !important}.rst-content dl:not(.docutils) dt:first-child{margin-top:0}.rst-content dl:not(.docutils) tt,.rst-content dl:not(.docutils) tt,.rst-content dl:not(.docutils) code{font-weight:bold}.rst-content dl:not(.docutils) tt.descname,.rst-content dl:not(.docutils) tt.descclassname,.rst-content dl:not(.docutils) tt.descname,.rst-content dl:not(.docutils) code.descname,.rst-content dl:not(.docutils) tt.descclassname,.rst-content dl:not(.docutils) code.descclassname{background-color:transparent;border:none;padding:0;font-size:100% !important}.rst-content dl:not(.docutils) tt.descname,.rst-content dl:not(.docutils) tt.descname,.rst-content dl:not(.docutils) code.descname{font-weight:bold}.rst-content dl:not(.docutils) .optional{display:inline-block;padding:0 4px;color:#000;font-weight:bold}.rst-content dl:not(.docutils) .property{display:inline-block;padding-right:8px}.rst-content .viewcode-link,.rst-content .viewcode-back{display:inline-block;color:#27AE60;font-size:80%;padding-left:24px}.rst-content .viewcode-back{display:block;float:right}.rst-content p.rubric{margin-bottom:12px;font-weight:bold}.rst-content tt.download,.rst-content code.download{background:inherit;padding:inherit;font-weight:normal;font-family:inherit;font-size:inherit;color:inherit;border:inherit;white-space:inherit}.rst-content tt.download span:first-child,.rst-content code.download span:first-child{-webkit-font-smoothing:subpixel-antialiased}.rst-content tt.download span:first-child:before,.rst-content code.download span:first-child:before{margin-right:4px}.rst-content .guilabel{border:1px solid #7fbbe3;background:#e7f2fa;font-size:80%;font-weight:700;border-radius:4px;padding:2.4px 6px;margin:auto 2px}.rst-content .versionmodified{font-style:italic}@media screen and (max-width: 480px){.rst-content .sidebar{width:100%}}span[id*='MathJax-Span']{color:#404040}.math{text-align:center}@font-face{font-family:"Lato";src:url("../fonts/Lato/lato-regular.eot");src:url("../fonts/Lato/lato-regular.eot?#iefix") format("embedded-opentype"),url("../fonts/Lato/lato-regular.woff2") format("woff2"),url("../fonts/Lato/lato-regular.woff") format("woff"),url("../fonts/Lato/lato-regular.ttf") format("truetype");font-weight:400;font-style:normal}@font-face{font-family:"Lato";src:url("../fonts/Lato/lato-bold.eot");src:url("../fonts/Lato/lato-bold.eot?#iefix") format("embedded-opentype"),url("../fonts/Lato/lato-bold.woff2") format("woff2"),url("../fonts/Lato/lato-bold.woff") format("woff"),url("../fonts/Lato/lato-bold.ttf") format("truetype");font-weight:700;font-style:normal}@font-face{font-family:"Lato";src:url("../fonts/Lato/lato-bolditalic.eot");src:url("../fonts/Lato/lato-bolditalic.eot?#iefix") format("embedded-opentype"),url("../fonts/Lato/lato-bolditalic.woff2") format("woff2"),url("../fonts/Lato/lato-bolditalic.woff") format("woff"),url("../fonts/Lato/lato-bolditalic.ttf") format("truetype");font-weight:700;font-style:italic}@font-face{font-family:"Lato";src:url("../fonts/Lato/lato-italic.eot");src:url("../fonts/Lato/lato-italic.eot?#iefix") format("embedded-opentype"),url("../fonts/Lato/lato-italic.woff2") format("woff2"),url("../fonts/Lato/lato-italic.woff") format("woff"),url("../fonts/Lato/lato-italic.ttf") format("truetype");font-weight:400;font-style:italic}@font-face{font-family:"Roboto Slab";font-style:normal;font-weight:400;src:url("../fonts/RobotoSlab/roboto-slab.eot");src:url("../fonts/RobotoSlab/roboto-slab-v7-regular.eot?#iefix") format("embedded-opentype"),url("../fonts/RobotoSlab/roboto-slab-v7-regular.woff2") format("woff2"),url("../fonts/RobotoSlab/roboto-slab-v7-regular.woff") format("woff"),url("../fonts/RobotoSlab/roboto-slab-v7-regular.ttf") format("truetype")}@font-face{font-family:"Roboto Slab";font-style:normal;font-weight:700;src:url("../fonts/RobotoSlab/roboto-slab-v7-bold.eot");src:url("../fonts/RobotoSlab/roboto-slab-v7-bold.eot?#iefix") format("embedded-opentype"),url("../fonts/RobotoSlab/roboto-slab-v7-bold.woff2") format("woff2"),url("../fonts/RobotoSlab/roboto-slab-v7-bold.woff") format("woff"),url("../fonts/RobotoSlab/roboto-slab-v7-bold.ttf") format("truetype")}
================================================
FILE: docs/_themes/sphinx_rtd_theme/static/js/theme.js
================================================
/* sphinx_rtd_theme version 0.4.1 | MIT license */
/* Built 20180727 10:07 */
require=function n(e,i,t){function o(s,a){if(!i[s]){if(!e[s]){var l="function"==typeof require&&require;if(!a&&l)return l(s,!0);if(r)return r(s,!0);var c=new Error("Cannot find module '"+s+"'");throw c.code="MODULE_NOT_FOUND",c}var u=i[s]={exports:{}};e[s][0].call(u.exports,function(n){var i=e[s][1][n];return o(i||n)},u,u.exports,n,e,i,t)}return i[s].exports}for(var r="function"==typeof require&&require,s=0;s "),n("table.docutils.footnote").wrap(""),n("table.docutils.citation").wrap("
"),n(".wy-menu-vertical ul").not(".simple").siblings("a").each(function(){var i=n(this);expand=n(' '),expand.on("click",function(n){return e.toggleCurrent(i),n.stopPropagation(),!1}),i.prepend(expand)})},reset:function(){var n=encodeURI(window.location.hash)||"#";try{var e=$(".wy-menu-vertical"),i=e.find('[href="'+n+'"]');if(0===i.length){var t=$('.document [id="'+n.substring(1)+'"]').closest("div.section");0===(i=e.find('[href="#'+t.attr("id")+'"]')).length&&(i=e.find('[href="#"]'))}i.length>0&&($(".wy-menu-vertical .current").removeClass("current"),i.addClass("current"),i.closest("li.toctree-l1").addClass("current"),i.closest("li.toctree-l1").parent().addClass("current"),i.closest("li.toctree-l1").addClass("current"),i.closest("li.toctree-l2").addClass("current"),i.closest("li.toctree-l3").addClass("current"),i.closest("li.toctree-l4").addClass("current"))}catch(o){console.log("Error expanding nav for anchor",o)}},onScroll:function(){this.winScroll=!1;var n=this.win.scrollTop(),e=n+this.winHeight,i=this.navBar.scrollTop()+(n-this.winPosition);n<0||e>this.docHeight||(this.navBar.scrollTop(i),this.winPosition=n)},onResize:function(){this.winResize=!1,this.winHeight=this.win.height(),this.docHeight=$(document).height()},hashChange:function(){this.linkScroll=!0,this.win.one("hashchange",function(){this.linkScroll=!1})},toggleCurrent:function(n){var e=n.closest("li");e.siblings("li.current").removeClass("current"),e.siblings().find("li.current").removeClass("current"),e.find("> ul li.current").removeClass("current"),e.toggleClass("current")}},"undefined"!=typeof window&&(window.SphinxRtdTheme={Navigation:e.exports.ThemeNav,StickyNav:e.exports.ThemeNav}),function(){for(var n=0,e=["ms","moz","webkit","o"],i=0;i
Read the Docs
v: {{ current_version }}
{% endif %}
================================================
FILE: docs/api.rst
================================================
===
API
===
.. contents:: Table of Contents
:local:
SocketIoServer
==============
This class contains logic for creating namespaces
and handle raw Engine.IO connections.
Methods
-------
hasNamespace
^^^^^^^^^^^^
Call this method to check if namespace is registered with server.
namespace
^^^^^^^^^
Call this method to create or retrieve a namespace instance.
Dynamic Namespaces
------------------
Call the ``namespace`` method with either a ``Pattern`` instance or
``SocketIoNamespaceProvider`` instance to create a dynamic namespace.
**Note** Make sure to account for the starting ``/``.
SocketIoNamespace
=================
This class represents a Socket.IO namespace instance.
Methods
-------
broadcast
^^^^^^^^^
Call this method to broadcast an event to one or many rooms.
Events
------
connect
^^^^^^^
This event is emitted when a new client connection is established.
**Arguments**
0. ``SocketIoSocket`` instance of socket
connection
^^^^^^^^^^
Same as ``connect`` event.
SocketIoSocket
==============
This class represents a socket to connected client.
**Note** A socket joins a room with it's own id by default.
Methods
-------
send
^^^^
Call this method to send an event to the client with optional acknowledge callback.
broadcast
^^^^^^^^^
Call this method to broadcast an event to all sockets in one or many rooms excluding this socket.
disconnect
^^^^^^^^^^
Call this method to disconnect the socket.
joinRoom
^^^^^^^^
Call this method to add this socket to one or more rooms.
leaveRoom
^^^^^^^^^
Call this method to remove this socket from one or more rooms.
leaveAllRooms
^^^^^^^^^^^^^
Call this method to remove this socket from all rooms.
Events
------
Any event sent from the client is emitted in addition to the events listed below.
disconnecting
^^^^^^^^^^^^^
This event is emitted before disconnecting from client.
disconnect
^^^^^^^^^^
This event is emitted after disconnecting from client.
error
^^^^^
This event is raised on error.
**Arguments**
0. ``String`` error reason/description
================================================
FILE: docs/conf.py
================================================
# -*- coding: utf-8 -*-
#
# Configuration file for the Sphinx documentation builder.
#
# This file does only contain a selection of the most common options. For a
# full list see the documentation:
# http://www.sphinx-doc.org/en/master/config
# -- Path setup --------------------------------------------------------------
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
#
# import os
# import sys
# sys.path.insert(0, os.path.abspath('.'))
# -- Project information -----------------------------------------------------
project = u'Socket.IO Java Server'
copyright = u'2018, Trinopoty Biswas'
author = u'Trinopoty Biswas'
# The short X.Y version
version = u''
# The full version, including alpha/beta/rc tags
release = u''
# -- General configuration ---------------------------------------------------
# If your documentation needs a minimal Sphinx version, state it here.
#
# needs_sphinx = '1.0'
# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = [
'sphinx.ext.todo',
'sphinx.ext.githubpages',
]
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
# The suffix(es) of source filenames.
# You can specify multiple suffix as a list of string:
#
# source_suffix = ['.rst', '.md']
source_suffix = '.rst'
# The master toctree document.
master_doc = 'index'
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
#
# This is also used if you do content translation via gettext catalogs.
# Usually you set "language" from the command line for these cases.
language = None
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
# This pattern also affects html_static_path and html_extra_path .
exclude_patterns = [u'_build', 'Thumbs.db', '.DS_Store']
# The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx'
# -- Options for HTML output -------------------------------------------------
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
#
html_theme = 'sphinx_rtd_theme'
html_theme_path = ['_themes']
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
# documentation.
#
# html_theme_options = {}
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ['_static']
# Custom sidebar templates, must be a dictionary that maps document names
# to template names.
#
# The default sidebars (for documents that don't match any pattern) are
# defined by theme itself. Builtin themes are using these templates by
# default: ``['localtoc.html', 'relations.html', 'sourcelink.html',
# 'searchbox.html']``.
#
# html_sidebars = {}
# -- Options for HTMLHelp output ---------------------------------------------
# Output file base name for HTML help builder.
htmlhelp_basename = 'SocketIOdoc'
# -- Options for LaTeX output ------------------------------------------------
latex_elements = {
# The paper size ('letterpaper' or 'a4paper').
#
# 'papersize': 'letterpaper',
# The font size ('10pt', '11pt' or '12pt').
#
# 'pointsize': '10pt',
# Additional stuff for the LaTeX preamble.
#
# 'preamble': '',
# Latex figure (float) alignment
#
# 'figure_align': 'htbp',
}
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title,
# author, documentclass [howto, manual, or own class]).
latex_documents = [
(master_doc, 'SocketIO.tex', u'Socket.IO Documentation',
u'Trinopoty Biswas', 'manual'),
]
# -- Options for manual page output ------------------------------------------
# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [
(master_doc, 'socketio', u'Socket.IO Documentation',
[author], 1)
]
# -- Options for Texinfo output ----------------------------------------------
# Grouping the document tree into Texinfo files. List of tuples
# (source start file, target name, title, author,
# dir menu entry, description, category)
texinfo_documents = [
(master_doc, 'SocketIO', u'Socket.IO Documentation',
author, 'SocketIO', 'One line description of project.',
'Miscellaneous'),
]
# -- Extension configuration -------------------------------------------------
# -- Options for todo extension ----------------------------------------------
# If true, `todo` and `todoList` produce output, else they produce nothing.
todo_include_todos = True
================================================
FILE: docs/index.rst
================================================
.. Socket.IO documentation master file, created by
sphinx-quickstart on Thu Aug 9 18:55:12 2018.
You can adapt this file completely to your liking, but it should at least
contain the root `toctree` directive.
Welcome to Socket.IO Java Server's documentation!
=================================================
.. toctree::
:maxdepth: 1
:caption: Contents:
install
using
api
javadocs/index
================================================
FILE: docs/install.rst
================================================
============
Installation
============
The latest artifact will be available on Maven Central soon.
Socket.IO Java Server Library
=============================
Maven
-----
Add the following dependency to your ``pom.xml``.::
io.socket
socket.io-server
4.0.1
Gradle
------
Add it as a gradle dependency in ``build.gradle``.::
compile ('io.socket:socket.io-server:4.0.1')
================================================
FILE: docs/javadocs/index.rst
================================================
========
Javadocs
========
================================================
FILE: docs/requirements.txt
================================================
sphinx
sphinx-autobuild
================================================
FILE: docs/using.rst
================================================
===============
Getting Started
===============
Create a servlet for handling incoming HTTP connections and call
``EngineIoServer`` class's ``handleRequest`` method on receiving an HTTP
request. Create a new instance of ``SocketIoServer`` using the ``EngineIoServer``
instance.
Example servlet
===============
Example servlet class::
@WebServlet("/socket.io/*")
public class SocketIoServlet extends HttpServlet {
private final EngineIoServer mEngineIoServer = new EngineIoServer();
private final SocketIoServer mSocketIoServer = new SocketIoServer(mEngineIoServer);
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws IOException {
mEngineIoServer.handleRequest(request, response);
}
}
In the example servlet above, a static instance of ``EngineIoServer`` is defined and
the method ``service`` is overridden to call ``handleRequest``.
Accept WebSocket Connection
===========================
Please refer to `Engine.IO documentation `_ for accepting WebSocket connection.
Create namesapce
================
Call the ``namespace`` method on ``SocketIoServer`` to create or retrieve a namespace.
Example::
SocketIoNamespace namespace = server.namespace("/");
// Do something with namespace
Listening for connections
=========================
Attach a listener to the ``connection`` event of ``SocketIoNamespace`` to listen for
new connections.
Example::
namespace.on("connection", new Emitter.Listener() {
@Override
public void call(Object... args) {
SocketIoSocket socket = (SocketIoSocket) args[0];
// Do something with socket
}
});
Listening for message from client
=================================
Attach an event listener on ``SocketIoSocket`` to listen for events from client.
Example::
// Attaching to 'foo' event
socket.on("foo", new Emitter.Listener() {
@Override
public void call(Object... args) {
// Arugments from client available in 'args'
}
});
Sending message to client
=========================
Call the ``send`` method on ``SocketIoSocket`` to send event to remote client.
Example::
// Sending event 'foo' with args 'bar arg', 1
socket.send("foo", "bar arg", 1);
Broadcasting message to room
============================
Call the ``broadcast`` method on ``SocketIoNamespace`` to broadcast event to
all remote clients.
Example::
// Broadcasting event 'foo' with args 'bar arg' to room 'room'
namespace.broadcast("room", "foo", "bar arg");
================================================
FILE: pom.xml
================================================
4.0.0
io.socket
socket.io-server-bom
4.1.2
pom
socket.io
Socket.IO server library for Java
https://github.com/trinopoty/socket.io-server-java
socket.io-server
socket.io-server-test
github
UTF-8
11
11
11.0.15
Apache License Version 2.0, January 2004
https://www.apache.org/licenses/LICENSE-2.0.txt
repo
https://github.com/trinopoty/socket.io-server-java
scm:git:https://github.com/trinopoty/socket.io-server-java.git
scm:git:https://github.com/trinopoty/socket.io-server-java.git
HEAD
trinopoty
Trinopoty Biswas
trinopoty@gmail.com
ossrh
https://oss.sonatype.org/content/repositories/snapshots
ossrh
https://oss.sonatype.org/service/local/staging/deploy/maven2/
io.socket
socket.io-server
${project.version}
release-sign-artifacts
performRelease
true
org.apache.maven.plugins
maven-gpg-plugin
3.1.0
sign-artifacts
verify
sign
${gpg.keyname}
org.apache.maven.plugins
maven-javadoc-plugin
3.5.0
aggregate
aggregate
site
org.codehaus.mojo
cobertura-maven-plugin
2.7
xml
true
org.apache.maven.plugins
maven-javadoc-plugin
3.0.1
aggregate
aggregate
org.codehaus.mojo
cobertura-maven-plugin
2.7
================================================
FILE: pom.xml.versionsBackup
================================================
4.0.0
io.socket
socket.io-server-bom
4.1.1
pom
socket.io
Socket.IO server library for Java
https://github.com/trinopoty/socket.io-server-java
socket.io-server
socket.io-server-test
github
UTF-8
11
11
11.0.15
Apache License Version 2.0, January 2004
https://www.apache.org/licenses/LICENSE-2.0.txt
repo
https://github.com/trinopoty/socket.io-server-java
scm:git:https://github.com/trinopoty/socket.io-server-java.git
scm:git:https://github.com/trinopoty/socket.io-server-java.git
HEAD
trinopoty
Trinopoty Biswas
trinopoty@gmail.com
ossrh
https://oss.sonatype.org/content/repositories/snapshots
ossrh
https://oss.sonatype.org/service/local/staging/deploy/maven2/
io.socket
socket.io-server
${project.version}
release-sign-artifacts
performRelease
true
org.apache.maven.plugins
maven-gpg-plugin
3.1.0
sign-artifacts
verify
sign
${gpg.keyname}
org.apache.maven.plugins
maven-javadoc-plugin
3.5.0
aggregate
aggregate
site
org.codehaus.mojo
cobertura-maven-plugin
2.7
xml
true
org.apache.maven.plugins
maven-javadoc-plugin
3.0.1
aggregate
aggregate
org.codehaus.mojo
cobertura-maven-plugin
2.7
================================================
FILE: socket.io-server/pom.xml
================================================
socket.io-server-bom
io.socket
4.1.2
4.0.0
socket.io-server
jar
socket.io-server
Socket.IO server library for Java
io.socket
engine.io-server
6.3.2
org.json
json
20230618
jakarta.platform
jakarta.jakartaee-api
10.0.0
provided
org.junit.platform
junit-platform-launcher
1.6.3
test
org.junit.jupiter
junit-jupiter-engine
5.6.3
test
org.junit.vintage
junit-vintage-engine
5.6.3
test
org.mockito
mockito-core
5.4.0
test
org.apache.maven.plugins
maven-compiler-plugin
3.11.0
1.8
1.8
UTF-8
-Xlint:unchecked
true
true
org.apache.maven.plugins
maven-source-plugin
3.3.0
attach-sources
jar-no-fork
org.apache.maven.plugins
maven-javadoc-plugin
3.5.0
attach-javadocs
jar
org.apache.maven.plugins
maven-jar-plugin
3.3.0
org.apache.maven.plugins
maven-surefire-plugin
3.1.2
-Dfile.encoding=UTF-8
java.util.logging.config.file
./src/test/resources/logging.properties
both
true
================================================
FILE: socket.io-server/pom.xml.versionsBackup
================================================
socket.io-server-bom
io.socket
4.1.1
4.0.0
socket.io-server
jar
socket.io-server
Socket.IO server library for Java
io.socket
engine.io-server
6.3.2
org.json
json
20230618
jakarta.platform
jakarta.jakartaee-api
10.0.0
provided
org.junit.platform
junit-platform-launcher
1.6.3
test
org.junit.jupiter
junit-jupiter-engine
5.6.3
test
org.junit.vintage
junit-vintage-engine
5.6.3
test
org.mockito
mockito-core
5.4.0
test
org.apache.maven.plugins
maven-compiler-plugin
3.11.0
1.8
1.8
UTF-8
-Xlint:unchecked
true
true
org.apache.maven.plugins
maven-source-plugin
3.3.0
attach-sources
jar-no-fork
org.apache.maven.plugins
maven-javadoc-plugin
3.5.0
attach-javadocs
jar
org.apache.maven.plugins
maven-jar-plugin
3.3.0
org.apache.maven.plugins
maven-surefire-plugin
3.1.2
-Dfile.encoding=UTF-8
java.util.logging.config.file
./src/test/resources/logging.properties
both
true
================================================
FILE: socket.io-server/src/main/java/io/socket/socketio/server/PacketUtils.java
================================================
package io.socket.socketio.server;
import io.socket.socketio.server.parser.Packet;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.Iterator;
@SuppressWarnings({"BooleanMethodIsAlwaysInverted"})
final class PacketUtils {
private static final Object[] EMPTY_ARGS = new Object[0];
/**
* Validate args and create packet.
*
* @param type Type of packet to create.
* @param event Name of event.
* @param args Data to set.
* @return Created packet.
* @throws IllegalArgumentException If args contain any invalid data type.
*/
@SuppressWarnings("SameParameterValue")
static Packet> createDataPacket(int type, String event, Object[] args) throws IllegalArgumentException {
if (args == null) {
args = EMPTY_ARGS;
}
final JSONArray array = new JSONArray();
if (event != null) {
array.put(event);
}
for (Object arg : args) {
array.put(arg);
}
if (!PacketUtils.isPacketDataValid(array)) {
throw new IllegalArgumentException("args contain invalid data type.");
}
final Packet packet = new Packet<>();
packet.type = type;
packet.data = array;
return packet;
}
@SuppressWarnings("Duplicates")
private static boolean isPacketDataValid(JSONArray array) {
try {
for (int idx = 0; idx < array.length(); idx++) {
final Object item = array.get(idx);
if (!isPacketDataValidType(item)) {
return false;
}
if (item == null) {
array.put(idx, JSONObject.NULL);
}
if ((item instanceof JSONArray) && !isPacketDataValid((JSONArray)item)) {
return false;
}
if ((item instanceof JSONObject) && !isPacketDataValid((JSONObject)item)) {
return false;
}
}
return true;
} catch (JSONException ignore) {
}
return false;
}
@SuppressWarnings("Duplicates")
private static boolean isPacketDataValid(JSONObject object) {
try {
final Iterator> keys = object.keys();
while (keys.hasNext()) {
final Object keyObj = keys.next();
if (!(keyObj instanceof String)) {
return false;
}
final String key = (String)keyObj;
final Object item = object.get(key);
if (!isPacketDataValidType(item)) {
return false;
}
if (item == null) {
object.put(key, JSONObject.NULL);
}
if ((item instanceof JSONArray) && !isPacketDataValid((JSONArray)item)) {
return false;
}
if ((item instanceof JSONObject) && !isPacketDataValid((JSONObject)item)) {
return false;
}
}
return true;
} catch (JSONException ignore) {
}
return false;
}
private static boolean isPacketDataValidType(Object object) {
return ((object == null) ||
(object == JSONObject.NULL) ||
(object instanceof JSONObject) ||
(object instanceof JSONArray) ||
(object instanceof CharSequence) ||
(object instanceof Number) ||
(object instanceof Boolean) ||
(object instanceof byte[]));
}
}
================================================
FILE: socket.io-server/src/main/java/io/socket/socketio/server/SocketIoAdapter.java
================================================
package io.socket.socketio.server;
import io.socket.engineio.server.Emitter;
import io.socket.socketio.server.parser.Packet;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
/**
* Socket.io adapter class for broadcasts.
*/
@SuppressWarnings("WeakerAccess")
public abstract class SocketIoAdapter extends Emitter {
/**
* Factory to create new instance of adapter.
*/
public interface AdapterFactory {
/**
* Create and return a new instance of adapter for a namespace.
*
* @param namespace The namespace to create adapter for.
* @return Adapter instance for namespace.
*/
SocketIoAdapter createAdapter(SocketIoNamespace namespace);
}
/**
* The namespace that this adapter serves.
*/
protected final SocketIoNamespace mNamespace;
/**
* Set of sockets contained within a room.
*/
protected final Map> mRoomSockets = new ConcurrentHashMap<>();
/**
* Set of rooms joined by a socket.
*/
protected final Map> mSocketRooms = new ConcurrentHashMap<>();
protected SocketIoAdapter(SocketIoNamespace namespace) {
mNamespace = namespace;
}
/**
* Broadcast a packet to all sockets or sockets that have joined
* specified rooms.
*
* @param packet Packet to broadcast.
* @param rooms List of rooms to restrict packet to or null to send to all rooms.
* @throws IllegalArgumentException If packet is null.
*/
public void broadcast(Packet> packet, String[] rooms) throws IllegalArgumentException {
broadcast(packet, rooms, null);
}
/**
* Broadcast a packet to all sockets or sockets that have joined
* specified rooms. Optionally, specify sockets to exclude from sending.
*
* @param packet Packet to broadcast.
* @param rooms List of rooms to restrict packet to or null to send to all rooms.
* @param socketsExcluded List of sockets to exclude from sending or null.
* @throws IllegalArgumentException If packet is null.
*/
public abstract void broadcast(Packet> packet, String[] rooms, String[] socketsExcluded) throws IllegalArgumentException;
/**
* Add a socket to the specified room.
*
* @param room Room name to add socket to.
* @param socket Socket to add to room.
* @throws IllegalArgumentException If room or socket is null.
*/
public abstract void add(String room, SocketIoSocket socket) throws IllegalArgumentException;
/**
* Remove a socket from the specified room.
*
* @param room Room name to remove socket from.
* @param socket Socket to remove from room.
* @throws IllegalArgumentException If room or socket is null.
*/
public abstract void remove(String room, SocketIoSocket socket) throws IllegalArgumentException;
/**
* Get list of sockets in specified room.
*
* @param room Room name to list sockets in.
* @return List of sockets or empty list.
* @throws IllegalArgumentException If room is null.
*/
public abstract SocketIoSocket[] listClients(String room) throws IllegalArgumentException;
/**
* Get list of rooms joined by socket.
*
* @param socket Socket to list rooms joined.
* @return List of rooms or empty list.
* @throws IllegalArgumentException If socket is null.
*/
public abstract String[] listClientRooms(SocketIoSocket socket) throws IllegalArgumentException;
}
================================================
FILE: socket.io-server/src/main/java/io/socket/socketio/server/SocketIoClient.java
================================================
package io.socket.socketio.server;
import io.socket.engineio.server.EngineIoSocket;
import io.socket.engineio.server.ReadyState;
import io.socket.socketio.server.parser.IOParser;
import io.socket.socketio.server.parser.Packet;
import io.socket.socketio.server.parser.Parser;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
/**
* Represents connection to one client.
* Be careful if interacting with this class directly, it might leave the connection in an invalid state.
*/
public final class SocketIoClient {
private final SocketIoServer mServer;
private final EngineIoSocket mConnection;
private final Parser.Encoder mEncoder;
private final Parser.Decoder mDecoder;
private final String mId;
private final Map mSockets = new ConcurrentHashMap<>();
private final Map mNamespaceSockets = new ConcurrentHashMap<>();
SocketIoClient(SocketIoServer server, EngineIoSocket connection) {
mServer = server;
mConnection = connection;
mEncoder = server.getEncoder();
mDecoder = new IOParser.Decoder();
mId = connection.getId();
setup();
}
/**
* Get id of this client.
*/
public String getId() {
return mId;
}
/**
* Get the query parameters of underlying engine.io connection.
*/
public Map getInitialQuery() {
return mConnection.getInitialQuery();
}
/**
* Get the headers of the underlying engine.io connection.
*/
public Map> getInitialHeaders() {
return mConnection.getInitialHeaders();
}
/**
* Sends a packet over the transport.
*
* @param packet Packet to send.
*/
public void sendPacket(final Packet> packet) {
if (mConnection.getReadyState() == ReadyState.OPEN) {
mEncoder.encode(packet, objects -> {
// TODO: Check for volatile flag
for (Object item : objects) {
final io.socket.engineio.server.parser.Packet engineIoPacket = new io.socket.engineio.server.parser.Packet<>(io.socket.engineio.server.parser.Packet.MESSAGE);
engineIoPacket.data = item;
mConnection.send(engineIoPacket);
}
});
}
}
/**
* Connects client to namespace.
*
* @param namespace Namespace to connect to.
*/
public void connect(String namespace, Object data) {
if (mServer.hasNamespace(namespace) || mServer.checkNamespace(namespace)) {
doConnect(namespace, data);
} else {
final JSONObject errorData = new JSONObject();
try {
errorData.put("message", "Invalid namespace");
} catch (JSONException ignore) {
}
final Packet packet = new Packet<>(Parser.CONNECT_ERROR);
packet.nsp = namespace;
packet.data = errorData;
sendPacket(packet);
}
}
/**
* Removes a socket.
*
* @param socket Socket to remove.
*/
public void remove(SocketIoSocket socket) {
if (mSockets.containsValue(socket)) {
final SocketIoNamespace namespace = socket.getNamespace();
mSockets.remove(socket.getId());
mNamespaceSockets.remove(namespace.getName());
}
}
/**
* Disconnect from all namespaces and close transport.
*/
public void disconnect() {
for (SocketIoSocket socket : mSockets.values()) {
socket.disconnect(false);
}
mSockets.clear();
close();
}
/**
* Get the underlying engine.io connection.
* @return Engine.IO connection object.
*/
public EngineIoSocket getConnection() {
return mConnection;
}
/**
* Close the connection.
*/
private void close() {
if (mConnection.getReadyState() == ReadyState.OPEN) {
mConnection.close();
onClose("forced server close");
}
}
private void setup() {
mDecoder.onDecoded(packet -> {
if (packet.type == IOParser.CONNECT) {
if (mConnection.getProtocolVersion() == 3) {
String namespace = packet.nsp;
String queryString = null;
if (namespace.contains("?")) {
queryString = namespace.substring(namespace.indexOf('?') + 1);
namespace = namespace.substring(0, namespace.indexOf('?'));
}
connect(namespace, queryString);
} else {
connect(packet.nsp, packet.data);
}
} else {
final SocketIoSocket socket = mNamespaceSockets.get(packet.nsp);
if (socket != null) {
socket.onPacket(packet);
}
}
});
mConnection.on("data", args -> {
try {
final Object data = args[0];
if (data instanceof String) {
mDecoder.add((String) data);
} else if(data instanceof byte[]) {
mDecoder.add((byte[]) data);
}
} catch (Exception ex) {
onError(ex.getMessage());
}
});
mConnection.on("error", args -> onError((String) args[0]));
mConnection.on("close", args -> onClose((String) args[0]));
mServer.getScheduledExecutor().schedule(() -> {
if (mNamespaceSockets.isEmpty()) {
close();
}
}, mServer.getOptions().getConnectionTimeout(), TimeUnit.MILLISECONDS);
}
private void destroy() {
mConnection.off("data");
mConnection.off("error");
mConnection.off("close");
}
private void doConnect(String namespace, Object data) {
final SocketIoNamespaceImpl nsp = (SocketIoNamespaceImpl)mServer.namespace(namespace);
final SocketIoSocket socket = nsp.add(this, data);
if (socket.isConnected()) {
mSockets.put(socket.getId(), socket);
mNamespaceSockets.put(namespace, socket);
}
}
private void onClose(String reason) {
destroy();
for (SocketIoSocket socket : mSockets.values()) {
socket.onClose(reason);
}
mSockets.clear();
mDecoder.destroy();
}
private void onError(String error) {
for (SocketIoSocket socket : mSockets.values()) {
socket.onError(error);
}
mConnection.close();
}
}
================================================
FILE: socket.io-server/src/main/java/io/socket/socketio/server/SocketIoMemoryAdapter.java
================================================
package io.socket.socketio.server;
import io.socket.socketio.server.parser.Packet;
import java.util.*;
/**
* In-memory adapter class.
* This is the default adapter used.
*/
public final class SocketIoMemoryAdapter extends SocketIoAdapter {
private static final String[] EMPTY_SOCKET_EXCLUSION = new String[0];
/**
* Factory for {@link SocketIoMemoryAdapter} class.
*/
public static final class Factory implements AdapterFactory {
@Override
public SocketIoAdapter createAdapter(SocketIoNamespace namespace) {
return new SocketIoMemoryAdapter(namespace);
}
}
private SocketIoMemoryAdapter(SocketIoNamespace namespace) {
super(namespace);
}
@Override
public synchronized void broadcast(Packet> packet, String[] rooms, String[] socketsExcluded) throws IllegalArgumentException {
if (packet == null) {
throw new IllegalArgumentException("packet must not be null.");
}
socketsExcluded = (socketsExcluded != null)? socketsExcluded : EMPTY_SOCKET_EXCLUSION;
final Set socketsExcludedSet = new HashSet<>();
Collections.addAll(socketsExcludedSet, socketsExcluded);
final Map connectedSockets = mNamespace.getConnectedSockets();
if (rooms != null) {
final Set sentSocketIds = new HashSet<>(); // To ensure only one packet is sent if socket is added to multiple rooms
for (String room : rooms) {
if (mRoomSockets.containsKey(room)) {
final Set sockets = mRoomSockets.get(room);
for (SocketIoSocket socket : sockets) {
if (!socketsExcludedSet.contains(socket.getId()) &&
!sentSocketIds.contains(socket.getId()) &&
connectedSockets.containsKey(socket.getId())) {
socket.sendPacket(packet);
sentSocketIds.add(socket.getId());
}
}
}
}
} else {
for (String socketId : mSocketRooms.keySet()) {
if (!socketsExcludedSet.contains(socketId)) {
final SocketIoSocket socket = connectedSockets.get(socketId);
if (socket != null) {
socket.sendPacket(packet);
}
}
}
}
}
@Override
public synchronized void add(String room, SocketIoSocket socket) throws IllegalArgumentException {
if (room == null) {
throw new IllegalArgumentException("room must not be null.");
}
if (socket == null) {
throw new IllegalArgumentException("socket must not be null.");
}
if (!mSocketRooms.containsKey(socket.getId())) {
mSocketRooms.put(socket.getId(), new HashSet<>());
}
if (!mRoomSockets.containsKey(room)) {
mRoomSockets.put(room, new HashSet<>());
}
mSocketRooms.get(socket.getId()).add(room);
mRoomSockets.get(room).add(socket);
}
@Override
public synchronized void remove(String room, SocketIoSocket socket) throws IllegalArgumentException {
if (room == null) {
throw new IllegalArgumentException("room must not be null.");
}
if (socket == null) {
throw new IllegalArgumentException("socket must not be null.");
}
if (mRoomSockets.containsKey(room)) {
final Set roomSockets = mRoomSockets.get(room);
roomSockets.remove(socket);
if (roomSockets.size() == 0) {
mRoomSockets.remove(room);
}
}
if (mSocketRooms.containsKey(socket.getId())) {
final Set socketRooms = mSocketRooms.get(socket.getId());
socketRooms.remove(room);
if (socketRooms.size() == 0) {
mSocketRooms.remove(socket.getId());
}
}
}
@Override
public SocketIoSocket[] listClients(String room) throws IllegalArgumentException {
if (room == null) {
throw new IllegalArgumentException("room must not be null.");
}
if (mRoomSockets.containsKey(room)) {
return mRoomSockets.get(room).toArray(new SocketIoSocket[0]);
} else {
return new SocketIoSocket[0];
}
}
@Override
public String[] listClientRooms(SocketIoSocket socket) throws IllegalArgumentException {
if (socket == null) {
throw new IllegalArgumentException("socket must not be null.");
}
if (mSocketRooms.containsKey(socket.getId())) {
return mSocketRooms.get(socket.getId()).toArray(new String[0]);
} else {
return new String[0];
}
}
}
================================================
FILE: socket.io-server/src/main/java/io/socket/socketio/server/SocketIoNamespace.java
================================================
package io.socket.socketio.server;
import io.socket.engineio.server.Emitter;
import java.util.Map;
/**
* Socket.io namespace class.
* This class represents a namespace created on the server.
*/
@SuppressWarnings("WeakerAccess")
public abstract class SocketIoNamespace extends Emitter {
protected final SocketIoServer mServer;
protected final String mName;
protected final SocketIoAdapter mAdapter;
SocketIoNamespace(SocketIoServer server, String name) {
mServer = server;
mName = name;
mAdapter = mServer.getAdapterFactory().createAdapter(this);
}
/**
* Get the name of this namespace.
*
* @return Namespace name with '/' prefix.
*/
public String getName() {
return mName;
}
/**
* Get the server associated with this namespace.
*
* @return Server instance of this namespace.
*/
public SocketIoServer getServer() {
return mServer;
}
/**
* Get the adapter for this namespace.
*
* @return Adapter instance for this namespace.
*/
public SocketIoAdapter getAdapter() {
return mAdapter;
}
/**
* Broadcast a message to all clients in this namespace that
* have joined specified room.
*
* @param room Room to send message to or null.
* @param event Name of event to raise on remote client.
* @param args Arguments to send. Supported types are: JSONObject, JSONArray, null
*/
public final void broadcast(String room, String event, Object... args) throws IllegalArgumentException {
broadcast((room != null)? (new String[] { room }) : null, event, args);
}
/**
* Broadcast a message to all clients in this namespace that
* have joined specified rooms.
*
* @param rooms Rooms to send message to.
* @param event Name of event to raise on remote client.
* @param args Array of arguments to send. Supported types are: JSONObject, JSONArray, null
* @throws IllegalArgumentException If event is null or argument is not of supported type.
*/
public abstract void broadcast(String[] rooms, String event, Object[] args) throws IllegalArgumentException;
abstract Map getConnectedSockets();
}
================================================
FILE: socket.io-server/src/main/java/io/socket/socketio/server/SocketIoNamespaceGroupImpl.java
================================================
package io.socket.socketio.server;
import io.socket.engineio.server.Emitter;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
final class SocketIoNamespaceGroupImpl extends SocketIoNamespace {
private static final AtomicInteger NAME_COUNTER = new AtomicInteger(0);
private final HashSet mChildNamespaces = new HashSet<>();
SocketIoNamespaceGroupImpl(SocketIoServer server) {
super(server, "/_" + NAME_COUNTER.incrementAndGet());
}
@Override
public void broadcast(String[] rooms, String event, Object[] args) throws IllegalArgumentException {
for (SocketIoNamespaceImpl namespace : mChildNamespaces) {
namespace.broadcast(rooms, event, args);
}
}
@Override
Map getConnectedSockets() {
final Map sockets = new HashMap<>();
for (SocketIoNamespaceImpl namespace : mChildNamespaces) {
sockets.putAll(namespace.getConnectedSockets());
}
return sockets;
}
SocketIoNamespaceImpl createChild(String name) {
final SocketIoNamespaceImpl nsp = new SocketIoNamespaceImpl(getServer(), name);
for (Emitter.Listener listener : listeners("connect")) {
nsp.on("connect", listener);
}
for (Emitter.Listener listener : listeners("connection")) {
nsp.on("connection", listener);
}
mChildNamespaces.add(nsp);
return nsp;
}
}
================================================
FILE: socket.io-server/src/main/java/io/socket/socketio/server/SocketIoNamespaceImpl.java
================================================
package io.socket.socketio.server;
import io.socket.engineio.server.ReadyState;
import io.socket.socketio.server.parser.Packet;
import io.socket.socketio.server.parser.Parser;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
/**
* Socket.io namespace class.
*/
final class SocketIoNamespaceImpl extends SocketIoNamespace {
@SuppressWarnings("MismatchedQueryAndUpdateOfCollection")
private final Map mSockets = new ConcurrentHashMap<>();
private final Map mConnectedSockets = new ConcurrentHashMap<>();
private final AtomicInteger mAckId = new AtomicInteger(0);
SocketIoNamespaceImpl(SocketIoServer server, String name) {
super(server, name);
}
@Override
public void broadcast(String[] rooms, String event, Object[] args) throws IllegalArgumentException {
if (event == null) {
throw new IllegalArgumentException("event cannot be null.");
}
final Packet packet = PacketUtils.createDataPacket(Parser.EVENT, event, args);
mAdapter.broadcast(packet, rooms);
}
@Override
Map getConnectedSockets() {
return mConnectedSockets;
}
/**
* Return an atomically increasing integer for packet id.
*
* @return Int value for use as packet id.
*/
int nextId() {
return mAckId.incrementAndGet();
}
/**
* Add a client instance to this namespace.
*
* @param client Client instance to add.
* @param data Data sent with the CONNECT packet.
* @return Socket instance created from client.
*/
synchronized SocketIoSocket add(SocketIoClient client, Object data) {
final SocketIoSocket socket = new SocketIoSocket(this, client, data);
if (client.getConnection().getReadyState() == ReadyState.OPEN) {
mSockets.put(socket.getId(), socket);
socket.onConnect();
emit("connect", socket);
emit("connection", socket);
}
return socket;
}
/**
* Remove a socket instance from this namespace.
*
* @param socket Socket instance to remove.
*/
synchronized void remove(SocketIoSocket socket) {
mSockets.remove(socket.getId());
}
/**
* Mark a socket as connected.
*
* @param socket Socket to mark as connected.
*/
synchronized void addConnected(SocketIoSocket socket) {
mConnectedSockets.put(socket.getId(), socket);
}
/**
* Mark a socket as not connected.
*
* @param socket Socket to mark as not connected.
*/
synchronized void removeConnected(SocketIoSocket socket) {
mConnectedSockets.remove(socket.getId());
}
}
================================================
FILE: socket.io-server/src/main/java/io/socket/socketio/server/SocketIoNamespaceProvider.java
================================================
package io.socket.socketio.server;
/**
* Provides methods for checking validity of dynamic namespaces.
*/
public interface SocketIoNamespaceProvider {
/**
* Check if the namespace provided is valid.
*
* @param namespace Namespace to check for validity.
* @return Boolean value indicating namespace validity.
*/
boolean checkNamespace(String namespace);
}
================================================
FILE: socket.io-server/src/main/java/io/socket/socketio/server/SocketIoServer.java
================================================
package io.socket.socketio.server;
import io.socket.engineio.server.EngineIoServer;
import io.socket.engineio.server.EngineIoSocket;
import io.socket.socketio.server.parser.IOParser;
import io.socket.socketio.server.parser.Parser;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledExecutorService;
import java.util.regex.Pattern;
/**
* The socket.io server.
*/
@SuppressWarnings("WeakerAccess")
public final class SocketIoServer {
private final SocketIoServerOptions mOptions;
private final Map mNamespaceRegexProviderMap = new ConcurrentHashMap<>();
private final Map mNamespaceGroups = new ConcurrentHashMap<>();
private final Map mNamespaces = new ConcurrentHashMap<>();
private final Parser.Encoder mEncoder = new IOParser.Encoder();
private final ScheduledExecutorService mScheduledExecutor;
/**
* Create instance of server with default options.
*
* @param server The underlying engine.io server.
*/
public SocketIoServer(EngineIoServer server) {
this(server, SocketIoServerOptions.DEFAULT);
}
/**
* Create instance of server with provided options.
*
* @param server The underlying engine.io server.
* @param options Server options.
*/
public SocketIoServer(EngineIoServer server, SocketIoServerOptions options) {
mOptions = options;
mOptions.lock();
mScheduledExecutor = server.getScheduledExecutor();
namespace("/");
server.on("connection", args -> {
final EngineIoSocket socket = (EngineIoSocket) args[0];
final SocketIoClient client = new SocketIoClient(SocketIoServer.this, socket);
if (socket.getProtocolVersion() == 3) {
client.connect("/", null);
}
});
}
SocketIoServerOptions getOptions() {
return mOptions;
}
ScheduledExecutorService getScheduledExecutor() {
return mScheduledExecutor;
}
/**
* Get the packet encoder of this server.
*
* @return Packet encoder instance.
*/
Parser.Encoder getEncoder() {
return mEncoder;
}
/**
* Gets the adapter factory of this server.
*
* @return Adapter factory instance.
*/
SocketIoAdapter.AdapterFactory getAdapterFactory() {
return mOptions.getAdapterFactory();
}
boolean checkNamespace(String namespace) {
if (namespace.charAt(0) != '/') {
namespace = "/" + namespace;
}
for (SocketIoNamespaceProvider provider : mNamespaceGroups.keySet()) {
if (provider.checkNamespace(namespace)) {
SocketIoNamespaceGroupImpl namespaceGroup = mNamespaceGroups.get(provider);
SocketIoNamespaceImpl nsp = namespaceGroup.createChild(namespace);
mNamespaces.put(namespace, nsp);
return true;
}
}
return false;
}
/**
* Checks if the given namespace has been created.
*
* @param namespace Name of namespace with or without '/' prefix.
* @return Boolean value indicating if namespace has been created or not.
*/
public boolean hasNamespace(String namespace) {
if (namespace.charAt(0) != '/') {
namespace = "/" + namespace;
}
return mNamespaces.containsKey(namespace);
}
/**
* Retrieve instance of namespace with specified name.
* This method creates the namespace if not already present.
*
* @param namespace Name of namespace with or without '/' prefix.
* @return Namespace instance.
*/
public synchronized SocketIoNamespace namespace(String namespace) {
if (namespace.charAt(0) != '/') {
namespace = "/" + namespace;
}
SocketIoNamespaceImpl nsp = mNamespaces.get(namespace);
if (nsp == null) {
nsp = new SocketIoNamespaceImpl(this, namespace);
mNamespaces.put(namespace, nsp);
}
return nsp;
}
public synchronized SocketIoNamespace namespace(SocketIoNamespaceProvider namespaceProvider) {
SocketIoNamespaceGroupImpl nsp = mNamespaceGroups.get(namespaceProvider);
if (nsp == null) {
nsp = new SocketIoNamespaceGroupImpl(this);
mNamespaceGroups.put(namespaceProvider, nsp);
}
return nsp;
}
public synchronized SocketIoNamespace namespace(final Pattern namespaceRegex) {
final SocketIoNamespaceProvider provider = mNamespaceRegexProviderMap.computeIfAbsent(
namespaceRegex,
r -> namespace -> r.matcher(namespace).matches());
return namespace(provider);
}
}
================================================
FILE: socket.io-server/src/main/java/io/socket/socketio/server/SocketIoServerOptions.java
================================================
package io.socket.socketio.server;
/**
* Options for {@link SocketIoServer}
*/
@SuppressWarnings("WeakerAccess")
public final class SocketIoServerOptions {
/**
* The default options used by server.
* This instance is locked and cannot be modified.
*
* adapter factory: {@link SocketIoMemoryAdapter.Factory}
*/
public static final SocketIoServerOptions DEFAULT = new SocketIoServerOptions();
private static final SocketIoAdapter.AdapterFactory MEMORY_ADAPTER_FACTORY = new SocketIoMemoryAdapter.Factory();
static {
DEFAULT.setAdapterFactory(MEMORY_ADAPTER_FACTORY);
DEFAULT.setConnectionTimeout(45000);
DEFAULT.lock();
}
private boolean mIsLocked;
private long mConnectionTimeout;
private SocketIoAdapter.AdapterFactory mAdapterFactory;
private SocketIoServerOptions() {
mIsLocked = false;
}
/**
* Create a new instance of {@link SocketIoServerOptions} by copying
* default options.
*
* @return New instance of {@link SocketIoServerOptions} with default options.
*/
public static SocketIoServerOptions newFromDefault() {
return (new SocketIoServerOptions())
.setConnectionTimeout(DEFAULT.getConnectionTimeout())
.setAdapterFactory(DEFAULT.getAdapterFactory());
}
/**
* Gets the amount of time to wait (ms) before a client without namespace is closed.
*/
public long getConnectionTimeout() {
return mConnectionTimeout;
}
/**
* Sets the amount of time to wait (ms) before a client without namespace is closed.
*/
public SocketIoServerOptions setConnectionTimeout(long connectionTimeout) {
mConnectionTimeout = connectionTimeout;
return this;
}
/**
* Gets the adapter factory instance.
*/
public SocketIoAdapter.AdapterFactory getAdapterFactory() {
return mAdapterFactory;
}
/**
* Sets the adapter factory instance.
*
* @param adapterFactory Adapter factory instance to set or null for default.
* @return Instance for chaining.
* @throws IllegalStateException If instance is locked.
*/
public SocketIoServerOptions setAdapterFactory(SocketIoAdapter.AdapterFactory adapterFactory) throws IllegalStateException {
if (mIsLocked) {
throw new IllegalStateException("Adapter factory cannot be set. Instance is locked.");
}
if (adapterFactory == null) {
adapterFactory = MEMORY_ADAPTER_FACTORY;
}
mAdapterFactory = adapterFactory;
return this;
}
/**
* Lock this options instance to prevent modifications.
*/
public void lock() {
mIsLocked = true;
}
}
================================================
FILE: socket.io-server/src/main/java/io/socket/socketio/server/SocketIoSocket.java
================================================
package io.socket.socketio.server;
import io.socket.engineio.server.Emitter;
import io.socket.engineio.server.utils.ServerYeast;
import io.socket.socketio.server.parser.Packet;
import io.socket.socketio.server.parser.Parser;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.*;
import java.util.concurrent.ConcurrentLinkedQueue;
/**
* Socket.io socket class.
* This class represents a unique combination of a single remote connection
* and it's associated namespace.
*/
@SuppressWarnings("WeakerAccess")
public final class SocketIoSocket extends Emitter {
private static final Object[] EMPTY_ARGS = new Object[0];
/**
* Callback for all user events received on socket.
*/
public interface AllEventListener {
/**
* Called for any user events received.
*
* @param eventName Name of event received.
* @param args Arguments of event received.
*/
void event(String eventName, Object... args);
}
/**
* Callback for remote received acknowledgement.
*/
public interface ReceivedByRemoteAcknowledgementCallback {
/**
* Called when remote client calls ack callback.
*
* @param args Data sent by remote client.
*/
void onReceivedByRemote(Object... args);
}
/**
* Callback for local received acknowledgement.
*/
public interface ReceivedByLocalAcknowledgementCallback {
/**
* Call this method to send ack to remote client.
*
* @param args Data sent to remote client.
*/
void sendAcknowledgement(Object... args);
}
private final ConcurrentLinkedQueue mAllEventListeners = new ConcurrentLinkedQueue<>();
private final SocketIoNamespaceImpl mNamespace;
private final SocketIoClient mClient;
private final SocketIoAdapter mAdapter;
private final String mId;
private final Object mConnectData;
private final HashSet mRooms = new HashSet<>();
private final HashMap mAcknowledgementCallbacks = new HashMap<>();
private boolean mConnected;
SocketIoSocket(SocketIoNamespaceImpl namespace, SocketIoClient client, Object connectData) {
mNamespace = namespace;
mClient = client;
mAdapter = namespace.getAdapter();
mConnectData = connectData;
if (client.getConnection().getProtocolVersion() == 3) {
mId = namespace.getName().equals("/") ? client.getId() : (namespace.getName() + "#" + client.getId());
} else {
mId = ServerYeast.yeast();
}
mConnected = true;
}
@Override
public int hashCode() {
return mId.hashCode();
}
@Override
public boolean equals(Object obj) {
return ((obj instanceof SocketIoSocket) && getId().equals(((SocketIoSocket)obj).getId()));
}
/**
* Gets the id of this socket.
*/
public String getId() {
return mId;
}
/**
* Gets the status of the connection.
*/
public boolean isConnected() {
return mConnected;
}
/**
* Gets the namespace of this socket.
*/
public SocketIoNamespace getNamespace() {
return mNamespace;
}
/**
* Gets the underlying client.
*/
public SocketIoClient getClient() {
return mClient;
}
/**
* Gets the data packet sent while establishing the socket connection.
*/
public Object getConnectData() {
return mConnectData;
}
/**
* Gets the query parameters of the initial HTTP connection.
*/
public Map getInitialQuery() {
return mClient.getInitialQuery();
}
/**
* Gets the headers of the initial HTTP connection.
*/
public Map> getInitialHeaders() {
return mClient.getInitialHeaders();
}
/**
* Disconnect this socket.
* Optionally, close the underlying connection.
*
* @param close Whether to close the remote connection or not.
*/
public void disconnect(boolean close) {
if (mConnected) {
if (close) {
mClient.disconnect();
} else {
final Packet> packet = new Packet<>();
packet.type = Parser.DISCONNECT;
sendPacket(packet);
onClose("server namespace disconnect");
}
}
}
/**
* Broadcast a message to all clients in this namespace that
* have joined specified room except this client.
*
* @param room Room to send message to.
* @param event Name of event to raise on remote client.
* @param args Arguments to send. Supported types are: JSONObject, JSONArray, null.
* @throws IllegalArgumentException If event is null or argument is not of supported type.
*/
public void broadcast(String room, String event, Object... args) throws IllegalArgumentException {
broadcast((room != null)? new String[] { room } : null, event, args);
}
/**
* Broadcast a message to all clients in this namespace that
* have joined specified rooms except this client.
*
* @param rooms Rooms to send message to.
* @param event Name of event to raise on remote client.
* @param args Array of arguments to send. Supported types are: JSONObject, JSONArray, null.
* @throws IllegalArgumentException If argument is not of supported type.
*/
public void broadcast(String[] rooms, String event, Object[] args) throws IllegalArgumentException {
if (event == null) {
throw new IllegalArgumentException("event cannot be null.");
}
final Packet> packet = PacketUtils.createDataPacket(Parser.EVENT, event, args);
mAdapter.broadcast(packet, rooms, new String[] { getId() });
}
/**
* Send data to remote client.
*
* @param event Name of event to raise on remote client.
* @param args Array of arguments to send. Supported types are: JSONObject, JSONArray, null.
* @throws IllegalArgumentException If event is null or argument is not of supported type.
*/
public void send(String event, Object... args) throws IllegalArgumentException {
send(event, args, null);
}
/**
* Send data to remote client.
*
* @param event Name of event to raise on remote client.
* @param args Array of arguments to send. Supported types are: JSONObject, JSONArray, null.
* @param acknowledgementCallback Acknowledgement callback to call on remote ack or null.
* @throws IllegalArgumentException If event is null or argument is not of supported type.
*/
public void send(String event, Object[] args, ReceivedByRemoteAcknowledgementCallback acknowledgementCallback) throws IllegalArgumentException {
if (event == null) {
throw new IllegalArgumentException("event cannot be null.");
}
final Packet> packet = PacketUtils.createDataPacket(Parser.EVENT, event, args);
if (acknowledgementCallback != null) {
packet.id = mNamespace.nextId();
mAcknowledgementCallbacks.put(packet.id, acknowledgementCallback);
}
sendPacket(packet);
}
/**
* Adds the socket to the specified rooms.
*
* @param rooms List of rooms to join.
*/
public synchronized void joinRoom(String... rooms) {
final ArrayList roomList = new ArrayList<>();
for (String room : rooms) {
if (!mRooms.contains(room)) {
roomList.add(room);
}
}
if (roomList.size() > 0) {
for (String room : roomList) {
mAdapter.add(room, this);
mRooms.add(room);
}
}
}
/**
* Removes the socket from the specified rooms.
*
* @param rooms List of rooms to leave.
*/
public synchronized void leaveRoom(String... rooms) {
final ArrayList roomList = new ArrayList<>();
for (String room : rooms) {
if (mRooms.contains(room)) {
roomList.add(room);
}
}
if (roomList.size() > 0) {
for (String room : roomList) {
mAdapter.remove(room, this);
mRooms.remove(room);
}
}
}
/**
* Removes the socket from all rooms.
*/
public synchronized void leaveAllRooms() {
for (String room : mRooms) {
mAdapter.remove(room, this);
}
mRooms.clear();
}
/**
* Register listener for all user events.
*
* @param listener Listener to register.
*/
public void registerAllEventListener(AllEventListener listener) {
mAllEventListeners.add(listener);
}
/**
* Unregister listener registered with registerAllEventListener.
*
* @param listener Listener to unregister.
*/
public void unregisterAllEventListener(AllEventListener listener) {
mAllEventListeners.remove(listener);
}
void onEvent(final Packet> packet) {
Object[] args = (packet.data != null)? unpackEventData((JSONArray)packet.data) : EMPTY_ARGS;
if (packet.id >= 0) {
final Object[] emitArgs = new Object[args.length + 1];
System.arraycopy(args, 0, emitArgs, 0, args.length);
emitArgs[args.length] = (ReceivedByLocalAcknowledgementCallback) args1 -> {
final Packet> ackPacket = PacketUtils.createDataPacket(Parser.ACK, null, args1);
ackPacket.id = packet.id;
sendPacket(ackPacket);
};
args = emitArgs;
}
final String event = args[0].toString();
final Object[] eventArgs = new Object[args.length - 1];
System.arraycopy(args, 1, eventArgs, 0, eventArgs.length);
emit(event, eventArgs);
for (AllEventListener listener : mAllEventListeners) {
listener.event(event, eventArgs);
}
}
void onAck(Packet> packet) {
if (mAcknowledgementCallbacks.containsKey(packet.id)) {
ReceivedByRemoteAcknowledgementCallback acknowledgement = mAcknowledgementCallbacks.get(packet.id);
mAcknowledgementCallbacks.remove(packet.id);
final Object[] args = (packet.data != null)? unpackEventData((JSONArray)packet.data) : EMPTY_ARGS;
acknowledgement.onReceivedByRemote(args);
}
}
void onPacket(Packet> packet) {
switch (packet.type) {
case Parser.EVENT:
case Parser.BINARY_EVENT:
onEvent(packet);
break;
case Parser.ACK:
case Parser.BINARY_ACK:
onAck(packet);
break;
case Parser.DISCONNECT:
onDisconnect();
break;
case Parser.CONNECT_ERROR:
onError((String) packet.data);
break;
}
}
void onConnect() {
mNamespace.addConnected(this);
joinRoom(getId());
final JSONObject data = new JSONObject();
try {
data.put("sid", getId());
} catch (JSONException ignore) {
}
sendPacket(new Packet<>(Parser.CONNECT, data));
}
void onDisconnect() {
onClose("client namespace disconnect");
}
void onClose(String reason) {
if (mConnected) {
mConnected = false;
emit("disconnecting", reason);
leaveAllRooms();
mNamespace.remove(this);
mClient.remove(this);
mNamespace.removeConnected(this);
emit("disconnect", reason);
}
}
void onError(String error) {
if (listeners("error").size() > 0) {
emit("error", error);
}
}
void sendPacket(Packet> packet) {
packet.nsp = mNamespace.getName();
mClient.sendPacket(packet);
}
private static Object[] unpackEventData(JSONArray data) {
Object[] result = new Object[data.length()];
for (int i = 0; i < result.length; i++) {
try {
result[i] = data.get(i);
if (result[i] == JSONObject.NULL) {
result[i] = null;
}
} catch (JSONException ex) {
throw new RuntimeException(ex);
}
}
return result;
}
}
================================================
FILE: socket.io-server/src/main/java/io/socket/socketio/server/parser/Binary.java
================================================
package io.socket.socketio.server.parser;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
public class Binary {
private static final String KEY_PLACEHOLDER = "_placeholder";
private static final String KEY_NUM = "num";
private static final Logger logger = Logger.getLogger(Binary.class.getName());
@SuppressWarnings("unchecked")
public static DeconstructedPacket deconstructPacket(Packet packet) {
List buffers = new ArrayList<>();
packet.data = _deconstructPacket(packet.data, buffers);
packet.attachments = buffers.size();
DeconstructedPacket result = new DeconstructedPacket();
result.packet = packet;
result.buffers = buffers.toArray(new byte[buffers.size()][]);
return result;
}
private static Object _deconstructPacket(Object data, List buffers) {
if (data == null) return null;
if (data instanceof byte[]) {
JSONObject placeholder = new JSONObject();
try {
placeholder.put(KEY_PLACEHOLDER, true);
placeholder.put(KEY_NUM, buffers.size());
} catch (JSONException e) {
logger.log(Level.WARNING, "An error occured while putting data to JSONObject", e);
return null;
}
buffers.add((byte[])data);
return placeholder;
} else if (data instanceof JSONArray) {
JSONArray newData = new JSONArray();
JSONArray _data = (JSONArray)data;
int len = _data.length();
for (int i = 0; i < len; i ++) {
try {
newData.put(i, _deconstructPacket(_data.get(i), buffers));
} catch (JSONException e) {
logger.log(Level.WARNING, "An error occured while putting packet data to JSONObject", e);
return null;
}
}
return newData;
} else if (data instanceof JSONObject) {
JSONObject newData = new JSONObject();
JSONObject _data = (JSONObject)data;
Iterator> iterator = _data.keys();
while (iterator.hasNext()) {
String key = (String)iterator.next();
try {
newData.put(key, _deconstructPacket(_data.get(key), buffers));
} catch (JSONException e) {
logger.log(Level.WARNING, "An error occured while putting data to JSONObject", e);
return null;
}
}
return newData;
}
return data;
}
@SuppressWarnings("unchecked")
public static Packet reconstructPacket(Packet packet, byte[][] buffers) {
packet.data = _reconstructPacket(packet.data, buffers);
packet.attachments = -1;
return packet;
}
private static Object _reconstructPacket(Object data, byte[][] buffers) {
if (data instanceof JSONArray) {
JSONArray _data = (JSONArray)data;
int len = _data.length();
for (int i = 0; i < len; i ++) {
try {
_data.put(i, _reconstructPacket(_data.get(i), buffers));
} catch (JSONException e) {
logger.log(Level.WARNING, "An error occured while putting packet data to JSONObject", e);
return null;
}
}
return _data;
} else if (data instanceof JSONObject) {
JSONObject _data = (JSONObject)data;
if (_data.optBoolean(KEY_PLACEHOLDER)) {
int num = _data.optInt(KEY_NUM, -1);
return num >= 0 && num < buffers.length ? buffers[num] : null;
}
Iterator> iterator = _data.keys();
while (iterator.hasNext()) {
String key = (String)iterator.next();
try {
_data.put(key, _reconstructPacket(_data.get(key), buffers));
} catch (JSONException e) {
logger.log(Level.WARNING, "An error occured while putting data to JSONObject", e);
return null;
}
}
return _data;
}
return data;
}
public static class DeconstructedPacket {
public Packet packet;
public byte[][] buffers;
}
}
================================================
FILE: socket.io-server/src/main/java/io/socket/socketio/server/parser/DecodingException.java
================================================
package io.socket.socketio.server.parser;
public class DecodingException extends RuntimeException {
public DecodingException(String message) {
super(message);
}
}
================================================
FILE: socket.io-server/src/main/java/io/socket/socketio/server/parser/IOParser.java
================================================
package io.socket.socketio.server.parser;
import io.socket.socketio.server.utils.HasBinary;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.json.JSONTokener;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
final public class IOParser implements Parser {
private static final Logger logger = Logger.getLogger(IOParser.class.getName());
private IOParser() {}
final public static class Encoder implements Parser.Encoder {
public Encoder() {}
@Override
public void encode(Packet obj, Callback callback) {
if ((obj.type == EVENT || obj.type == ACK) && HasBinary.hasBinary(obj.data)) {
obj.type = obj.type == EVENT ? BINARY_EVENT : BINARY_ACK;
}
logger.fine(String.format("encoding packet %s", obj));
if (BINARY_EVENT == obj.type || BINARY_ACK == obj.type) {
encodeAsBinary(obj, callback);
} else {
String encoding = encodeAsString(obj);
callback.call(new String[] {encoding});
}
}
private String encodeAsString(Packet obj) {
StringBuilder str = new StringBuilder("" + obj.type);
if (BINARY_EVENT == obj.type || BINARY_ACK == obj.type) {
str.append(obj.attachments);
str.append("-");
}
if (obj.nsp != null && obj.nsp.length() != 0 && !"/".equals(obj.nsp)) {
str.append(obj.nsp);
str.append(",");
}
if (obj.id >= 0) {
str.append(obj.id);
}
if (obj.data != null) {
str.append(obj.data);
}
logger.fine(String.format("encoded %s as %s", obj, str));
return str.toString();
}
private void encodeAsBinary(Packet obj, Callback callback) {
Binary.DeconstructedPacket deconstruction = Binary.deconstructPacket(obj);
String pack = encodeAsString(deconstruction.packet);
List buffers = new ArrayList(Arrays.asList(deconstruction.buffers));
buffers.add(0, pack);
callback.call(buffers.toArray());
}
}
final public static class Decoder implements Parser.Decoder {
/*package*/ BinaryReconstructor reconstructor;
private Decoder.Callback onDecodedCallback;
public Decoder() {
this.reconstructor = null;
}
@Override
public void add(String obj) {
Packet packet = decodeString(obj);
if (BINARY_EVENT == packet.type || BINARY_ACK == packet.type) {
this.reconstructor = new BinaryReconstructor(packet);
if (this.reconstructor.reconPack.attachments == 0) {
if (this.onDecodedCallback != null) {
this.onDecodedCallback.call(packet);
}
}
} else {
if (this.onDecodedCallback != null) {
this.onDecodedCallback.call(packet);
}
}
}
@Override
public void add(byte[] obj) {
if (this.reconstructor == null) {
throw new RuntimeException("got binary data when not reconstructing a packet");
} else {
Packet packet = this.reconstructor.takeBinaryData(obj);
if (packet != null) {
this.reconstructor = null;
if (this.onDecodedCallback != null) {
this.onDecodedCallback.call(packet);
}
}
}
}
private static Packet decodeString(String str) {
int i = 0;
int length = str.length();
Packet p = new Packet<>(Character.getNumericValue(str.charAt(0)));
if (p.type < 0 || p.type > types.length - 1) {
throw new DecodingException("unknown packet type " + p.type);
}
if (BINARY_EVENT == p.type || BINARY_ACK == p.type) {
if (!str.contains("-") || length <= i + 1) {
throw new DecodingException("illegal attachments");
}
StringBuilder attachments = new StringBuilder();
while (str.charAt(++i) != '-') {
attachments.append(str.charAt(i));
}
p.attachments = Integer.parseInt(attachments.toString());
}
if (length > i + 1 && '/' == str.charAt(i + 1)) {
StringBuilder nsp = new StringBuilder();
while (true) {
++i;
char c = str.charAt(i);
if (',' == c) break;
nsp.append(c);
if (i + 1 == length) break;
}
p.nsp = nsp.toString();
} else {
p.nsp = "/";
}
if (length > i + 1){
Character next = str.charAt(i + 1);
if (Character.getNumericValue(next) > -1) {
StringBuilder id = new StringBuilder();
while (true) {
++i;
char c = str.charAt(i);
if (Character.getNumericValue(c) < 0) {
--i;
break;
}
id.append(c);
if (i + 1 == length) break;
}
try {
p.id = Integer.parseInt(id.toString());
} catch (NumberFormatException e){
throw new DecodingException("invalid payload");
}
}
}
if (length > i + 1){
try {
str.charAt(++i);
p.data = new JSONTokener(str.substring(i)).nextValue();
} catch (JSONException e) {
logger.log(Level.WARNING, "An error occured while retrieving data from JSONTokener", e);
throw new DecodingException("invalid payload");
}
if (!isPayloadValid(p.type, p.data)) {
throw new DecodingException("invalid payload");
}
}
logger.fine(String.format("decoded %s as %s", str, p));
return p;
}
private static boolean isPayloadValid(int type, Object payload) {
switch (type) {
case Parser.CONNECT:
case Parser.CONNECT_ERROR:
return payload instanceof JSONObject;
case Parser.DISCONNECT:
return payload == null;
case Parser.EVENT:
case Parser.BINARY_EVENT:
return payload instanceof JSONArray
&& ((JSONArray) payload).length() > 0
&& !((JSONArray) payload).isNull(0);
case Parser.ACK:
case Parser.BINARY_ACK:
return payload instanceof JSONArray;
default:
return false;
}
}
@Override
public void destroy() {
if (this.reconstructor != null) {
this.reconstructor.finishReconstruction();
}
this.onDecodedCallback = null;
}
@Override
public void onDecoded (Callback callback) {
this.onDecodedCallback = callback;
}
}
/*package*/ static class BinaryReconstructor {
public Packet reconPack;
/*package*/ List buffers;
BinaryReconstructor(Packet packet) {
this.reconPack = packet;
this.buffers = new ArrayList<>();
}
public Packet takeBinaryData(byte[] binData) {
this.buffers.add(binData);
if (this.buffers.size() == this.reconPack.attachments) {
Packet packet = Binary.reconstructPacket(this.reconPack,
this.buffers.toArray(new byte[this.buffers.size()][]));
this.finishReconstruction();
return packet;
}
return null;
}
public void finishReconstruction () {
this.reconPack = null;
this.buffers = new ArrayList<>();
}
}
}
================================================
FILE: socket.io-server/src/main/java/io/socket/socketio/server/parser/Packet.java
================================================
package io.socket.socketio.server.parser;
public class Packet {
public int type = -1;
public int id = -1;
public String nsp;
public T data;
public int attachments;
public Packet() {}
public Packet(int type) {
this.type = type;
}
public Packet(int type, T data) {
this.type = type;
this.data = data;
}
}
================================================
FILE: socket.io-server/src/main/java/io/socket/socketio/server/parser/Parser.java
================================================
package io.socket.socketio.server.parser;
public interface Parser {
/**
* Packet type `connect`.
*/
int CONNECT = 0;
/**
* Packet type `disconnect`.
*/
int DISCONNECT = 1;
/**
* Packet type `event`.
*/
int EVENT = 2;
/**
* Packet type `ack`.
*/
int ACK = 3;
/**
* Packet type `error`.
*/
int CONNECT_ERROR = 4;
/**
* Packet type `binary event`.
*/
int BINARY_EVENT = 5;
/**
* Packet type `binary ack`.
*/
int BINARY_ACK = 6;
int protocol = 5;
/**
* Packet types.
*/
String[] types = new String[] {
"CONNECT",
"DISCONNECT",
"EVENT",
"ACK",
"ERROR",
"BINARY_EVENT",
"BINARY_ACK"
};
interface Encoder {
void encode(Packet obj, Callback callback);
interface Callback {
void call(Object[] data);
}
}
interface Decoder {
void add(String obj);
void add(byte[] obj);
void destroy();
void onDecoded(Callback callback);
interface Callback {
void call(Packet packet);
}
}
}
================================================
FILE: socket.io-server/src/main/java/io/socket/socketio/server/utils/HasBinary.java
================================================
package io.socket.socketio.server.utils;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.Iterator;
import java.util.logging.Level;
import java.util.logging.Logger;
public interface HasBinary {
Logger logger = Logger.getLogger(HasBinary.class.getName());
static boolean hasBinary(Object data) {
if (data == null) return false;
if (data instanceof byte[]) {
return true;
}
if (data instanceof JSONArray) {
JSONArray _obj = (JSONArray)data;
int length = _obj.length();
for (int i = 0; i < length; i++) {
Object v;
try {
v = _obj.isNull(i) ? null : _obj.get(i);
} catch (JSONException e) {
logger.log(Level.WARNING, "An error occured while retrieving data from JSONArray", e);
return false;
}
if (hasBinary(v)) {
return true;
}
}
} else if (data instanceof JSONObject) {
JSONObject _obj = (JSONObject)data;
Iterator keys = _obj.keys();
while (keys.hasNext()) {
String key = keys.next();
Object v;
try {
v = _obj.get(key);
} catch (JSONException e) {
logger.log(Level.WARNING, "An error occured while retrieving data from JSONObject", e);
return false;
}
if (hasBinary(v)) {
return true;
}
}
}
return false;
}
}
================================================
FILE: socket.io-server/src/test/java/io/socket/socketio/server/SocketIoAdapterImpl.java
================================================
package io.socket.socketio.server;
import io.socket.socketio.server.parser.Packet;
public final class SocketIoAdapterImpl extends SocketIoAdapter {
SocketIoAdapterImpl(SocketIoNamespace namespace) {
super(namespace);
}
@Override
public void broadcast(Packet packet, String[] rooms) throws IllegalArgumentException {
super.broadcast(packet, rooms);
}
@Override
public void broadcast(Packet packet, String[] rooms, String[] socketsExcluded) throws IllegalArgumentException {
}
@Override
public void add(String room, SocketIoSocket socket) throws IllegalArgumentException {
}
@Override
public void remove(String room, SocketIoSocket socket) throws IllegalArgumentException {
}
@Override
public SocketIoSocket[] listClients(String room) throws IllegalArgumentException {
return new SocketIoSocket[0];
}
@Override
public String[] listClientRooms(SocketIoSocket socket) throws IllegalArgumentException {
return new String[0];
}
}
================================================
FILE: socket.io-server/src/test/java/io/socket/socketio/server/SocketIoAdapterTest.java
================================================
package io.socket.socketio.server;
import io.socket.socketio.server.parser.Packet;
import io.socket.socketio.server.parser.Parser;
import org.junit.Test;
import org.mockito.Mockito;
public final class SocketIoAdapterTest {
@Test
public void test_broadcast() {
final SocketIoAdapter adapter = Mockito.spy(new SocketIoAdapter(null) {
@Override
public void broadcast(Packet> packet, String[] rooms, String[] socketsExcluded) throws IllegalArgumentException {
}
@Override
public void add(String room, SocketIoSocket socket) throws IllegalArgumentException {
}
@Override
public void remove(String room, SocketIoSocket socket) throws IllegalArgumentException {
}
@Override
public SocketIoSocket[] listClients(String room) throws IllegalArgumentException {
return new SocketIoSocket[0];
}
@Override
public String[] listClientRooms(SocketIoSocket socket) throws IllegalArgumentException {
return new String[0];
}
});
Mockito.doCallRealMethod().when(adapter)
.broadcast(Mockito.any(Packet.class), Mockito.any(String[].class));
Mockito.doAnswer(invocationOnMock -> null)
.when(adapter)
.broadcast(Mockito.any(Packet.class), Mockito.any(String[].class), Mockito.any(String[].class));
adapter.broadcast(new Packet<>(Parser.CONNECT), null);
Mockito.verify(adapter, Mockito.times(1))
.broadcast(Mockito.any(Packet.class), Mockito.isNull(), Mockito.isNull());
}
}
================================================
FILE: socket.io-server/src/test/java/io/socket/socketio/server/SocketIoClientTest.java
================================================
package io.socket.socketio.server;
import io.socket.engineio.server.Emitter;
import io.socket.engineio.server.EngineIoServer;
import io.socket.engineio.server.EngineIoSocket;
import io.socket.engineio.server.EngineIoWebSocket;
import io.socket.socketio.server.parser.Packet;
import io.socket.socketio.server.parser.Parser;
import org.json.JSONArray;
import org.junit.Test;
import org.mockito.Mockito;
import java.io.IOException;
import static org.junit.Assert.assertEquals;
public final class SocketIoClientTest {
@Test
public void test_constructor() {
final EngineIoServer engineIoServer = new EngineIoServer();
final SocketIoServer socketIoServer = new SocketIoServer(engineIoServer);
final Emitter.Listener connectionListener = Mockito.mock(Emitter.Listener.class);
Mockito.doAnswer(invocation -> {
final Object[] args = invocation.getArguments();
final EngineIoSocket socket = Mockito.spy((EngineIoSocket) args[0]);
final SocketIoClient client = new SocketIoClient(socketIoServer, socket);
assertEquals(socket.getId(), client.getId());
assertEquals(socket, client.getConnection());
Mockito.verify(socket, Mockito.times(1))
.on(Mockito.eq("data"), Mockito.any(Emitter.Listener.class));
Mockito.verify(socket, Mockito.times(1))
.on(Mockito.eq("error"), Mockito.any(Emitter.Listener.class));
Mockito.verify(socket, Mockito.times(1))
.on(Mockito.eq("close"), Mockito.any(Emitter.Listener.class));
return null;
}).when(connectionListener).call(Mockito.any());
engineIoServer.on("connection", connectionListener);
engineIoServer.handleWebSocket(new StubEngineIoWebSocket());
Mockito.verify(connectionListener, Mockito.times(1))
.call(Mockito.any(EngineIoSocket.class));
}
@Test
public void test_sendPacket_nonbinary() {
final EngineIoServer engineIoServer = new EngineIoServer();
final SocketIoServer socketIoServer = new SocketIoServer(engineIoServer);
final EngineIoWebSocket webSocket = Mockito.spy(new StubEngineIoWebSocket());
final Emitter.Listener connectionListener = Mockito.mock(Emitter.Listener.class);
Mockito.doAnswer(invocation -> {
final Object[] args = invocation.getArguments();
final EngineIoSocket socket = (EngineIoSocket) args[0];
final SocketIoClient client = new SocketIoClient(socketIoServer, socket);
final JSONArray packetData = new JSONArray();
packetData.put("foo");
packetData.put(1);
final Packet packet = new Packet<>(Parser.EVENT, packetData);
try {
Mockito.reset(webSocket);
client.sendPacket(packet);
Mockito.verify(webSocket, Mockito.times(1))
.write(Mockito.anyString());
} catch (IOException ignore) {
}
return null;
}).when(connectionListener).call(Mockito.any());
engineIoServer.on("connection", connectionListener);
engineIoServer.handleWebSocket(webSocket);
Mockito.verify(connectionListener, Mockito.times(1))
.call(Mockito.any(EngineIoSocket.class));
}
@Test
public void test_sendPacket_binary() {
final EngineIoServer engineIoServer = new EngineIoServer();
final SocketIoServer socketIoServer = new SocketIoServer(engineIoServer);
final EngineIoWebSocket webSocket = Mockito.spy(new StubEngineIoWebSocket());
final Emitter.Listener connectionListener = Mockito.mock(Emitter.Listener.class);
Mockito.doAnswer(invocation -> {
final Object[] args = invocation.getArguments();
final EngineIoSocket socket = (EngineIoSocket) args[0];
final SocketIoClient client = new SocketIoClient(socketIoServer, socket);
final JSONArray packetData = new JSONArray();
packetData.put("foo");
packetData.put(1);
packetData.put(new byte[16]);
final Packet packet = new Packet<>(Parser.EVENT, packetData);
try {
Mockito.reset(webSocket);
client.sendPacket(packet);
Mockito.verify(webSocket, Mockito.times(1))
.write(Mockito.anyString());
Mockito.verify(webSocket, Mockito.times(1))
.write(Mockito.any(byte[].class));
} catch (IOException ignore) {
}
return null;
}).when(connectionListener).call(Mockito.any());
engineIoServer.on("connection", connectionListener);
engineIoServer.handleWebSocket(webSocket);
Mockito.verify(connectionListener, Mockito.times(1))
.call(Mockito.any(EngineIoSocket.class));
}
@Test
public void test_sendPacket_after_close() {
final EngineIoServer engineIoServer = new EngineIoServer();
final SocketIoServer socketIoServer = new SocketIoServer(engineIoServer);
final EngineIoWebSocket webSocket = Mockito.spy(new StubEngineIoWebSocket());
final Emitter.Listener connectionListener = Mockito.mock(Emitter.Listener.class);
Mockito.doAnswer(invocation -> {
final Object[] args = invocation.getArguments();
final EngineIoSocket socket = (EngineIoSocket) args[0];
final SocketIoClient client = new SocketIoClient(socketIoServer, socket);
final JSONArray packetData = new JSONArray();
packetData.put("foo");
packetData.put(1);
socket.close();
final Packet packet = new Packet<>(Parser.EVENT, packetData);
try {
Mockito.reset(webSocket);
client.sendPacket(packet);
Mockito.verify(webSocket, Mockito.times(0))
.write(Mockito.anyString());
} catch (IOException ignore) {
}
return null;
}).when(connectionListener).call(Mockito.any());
engineIoServer.on("connection", connectionListener);
engineIoServer.handleWebSocket(webSocket);
Mockito.verify(connectionListener, Mockito.times(1))
.call(Mockito.any(EngineIoSocket.class));
}
@Test
public void test_disconnect() {
final EngineIoServer engineIoServer = new EngineIoServer();
final SocketIoServer socketIoServer = new SocketIoServer(engineIoServer);
final EngineIoWebSocket webSocket = Mockito.spy(new StubEngineIoWebSocket());
final Emitter.Listener connectionListener = Mockito.mock(Emitter.Listener.class);
Mockito.doAnswer(invocation -> {
final Object[] args = invocation.getArguments();
final EngineIoSocket socket = Mockito.spy((EngineIoSocket) args[0]);
final SocketIoClient client = new SocketIoClient(socketIoServer, socket);
client.disconnect();
Mockito.verify(socket, Mockito.times(1))
.close();
return null;
}).when(connectionListener).call(Mockito.any());
engineIoServer.on("connection", connectionListener);
engineIoServer.handleWebSocket(webSocket);
Mockito.verify(connectionListener, Mockito.times(1))
.call(Mockito.any(EngineIoSocket.class));
}
@Test
public void test_socket_error() {
final EngineIoServer engineIoServer = new EngineIoServer();
final SocketIoServer socketIoServer = new SocketIoServer(engineIoServer);
final EngineIoWebSocket webSocket = Mockito.spy(new StubEngineIoWebSocket());
final Emitter.Listener connectionListener = Mockito.mock(Emitter.Listener.class);
Mockito.doAnswer(invocation -> {
final Object[] args = invocation.getArguments();
final EngineIoSocket socket = Mockito.spy((EngineIoSocket) args[0]);
final SocketIoClient client = new SocketIoClient(socketIoServer, socket);
socket.emit("error", "parse error", null);
Mockito.verify(socket, Mockito.times(1))
.close();
return null;
}).when(connectionListener).call(Mockito.any());
engineIoServer.on("connection", connectionListener);
engineIoServer.handleWebSocket(webSocket);
Mockito.verify(connectionListener, Mockito.times(1))
.call(Mockito.any(EngineIoSocket.class));
}
}
================================================
FILE: socket.io-server/src/test/java/io/socket/socketio/server/SocketIoMemoryAdapterTest.java
================================================
package io.socket.socketio.server;
import io.socket.engineio.server.utils.ServerYeast;
import io.socket.socketio.server.parser.Packet;
import io.socket.socketio.server.parser.Parser;
import org.junit.Test;
import org.mockito.Mockito;
import java.util.HashMap;
import static org.junit.Assert.assertEquals;
public final class SocketIoMemoryAdapterTest {
private static final SocketIoMemoryAdapter.AdapterFactory ADAPTER_FACTORY = new SocketIoMemoryAdapter.Factory();
@Test(expected = IllegalArgumentException.class)
public void test_add_exception_on_null_room() {
final SocketIoAdapter adapter = ADAPTER_FACTORY.createAdapter(null);
adapter.add(null, Mockito.mock(SocketIoSocket.class));
}
@Test(expected = IllegalArgumentException.class)
public void test_add_exception_on_null_socket() {
final SocketIoAdapter adapter = ADAPTER_FACTORY.createAdapter(null);
adapter.add("foo", null);
}
@Test
public void test_add_new_socket_new_room() {
final SocketIoAdapter adapter = ADAPTER_FACTORY.createAdapter(null);
final SocketIoSocket socket = createDummySocket();
adapter.add("foo", socket);
assertEquals(1, adapter.listClients("foo").length);
assertEquals(1, adapter.listClientRooms(socket).length);
}
@Test
public void test_add_existing_socket_new_room() {
final SocketIoAdapter adapter = ADAPTER_FACTORY.createAdapter(null);
final SocketIoSocket socket = createDummySocket();
adapter.add("foo", socket);
adapter.add("bar", socket);
assertEquals(1, adapter.listClients("foo").length);
assertEquals(1, adapter.listClients("bar").length);
assertEquals(2, adapter.listClientRooms(socket).length);
}
@Test
public void test_add_new_socket_existing_room() {
final SocketIoAdapter adapter = ADAPTER_FACTORY.createAdapter(null);
final SocketIoSocket socket1 = createDummySocket();
final SocketIoSocket socket2 = createDummySocket();
adapter.add("foo", socket1);
adapter.add("foo", socket2);
assertEquals(2, adapter.listClients("foo").length);
assertEquals(0, adapter.listClients("bar").length);
assertEquals(1, adapter.listClientRooms(socket1).length);
assertEquals(1, adapter.listClientRooms(socket2).length);
}
@Test
public void test_add_existing_socket_existing_room() {
final SocketIoAdapter adapter = ADAPTER_FACTORY.createAdapter(null);
final SocketIoSocket socket = createDummySocket();
adapter.add("foo", socket);
assertEquals(1, adapter.listClients("foo").length);
assertEquals(1, adapter.listClientRooms(socket).length);
adapter.add("foo", socket);
assertEquals(1, adapter.listClients("foo").length);
assertEquals(1, adapter.listClientRooms(socket).length);
}
@Test
public void test_add_existing_socket_existing_room_multiple() {
final SocketIoAdapter adapter = ADAPTER_FACTORY.createAdapter(null);
final SocketIoSocket socket1 = createDummySocket();
final SocketIoSocket socket2 = createDummySocket();
adapter.add("foo", socket1);
adapter.add("foo", socket2);
adapter.add("bar", socket2);
assertEquals(2, adapter.listClients("foo").length);
assertEquals(1, adapter.listClients("bar").length);
assertEquals(1, adapter.listClientRooms(socket1).length);
assertEquals(2, adapter.listClientRooms(socket2).length);
}
@Test(expected = IllegalArgumentException.class)
public void test_remove_exception_on_null_room() {
final SocketIoAdapter adapter = ADAPTER_FACTORY.createAdapter(null);
adapter.remove(null, Mockito.mock(SocketIoSocket.class));
}
@Test(expected = IllegalArgumentException.class)
public void test_remove_exception_on_null_socket() {
final SocketIoAdapter adapter = ADAPTER_FACTORY.createAdapter(null);
adapter.remove("foo", null);
}
@Test
public void test_remove_unknown_socket_unknown_room() {
final SocketIoAdapter adapter = ADAPTER_FACTORY.createAdapter(null);
final SocketIoSocket socket = createDummySocket();
adapter.remove("foo", socket);
assertEquals(0, adapter.listClients("foo").length);
}
@Test
public void test_remove_known_socket_unknown_room() {
final SocketIoAdapter adapter = ADAPTER_FACTORY.createAdapter(null);
final SocketIoSocket socket = createDummySocket();
adapter.add("foo", socket);
adapter.remove("bar", socket);
assertEquals(1, adapter.listClients("foo").length);
assertEquals(0, adapter.listClients("bar").length);
}
@Test
public void test_remove_known_socket_known_room() {
final SocketIoAdapter adapter = ADAPTER_FACTORY.createAdapter(null);
final SocketIoSocket socket = createDummySocket();
adapter.add("foo", socket);
assertEquals(1, adapter.listClients("foo").length);
adapter.remove("foo", socket);
assertEquals(0, adapter.listClients("foo").length);
}
@Test
public void test_remove_known_socket_known_room_multiple() {
final SocketIoAdapter adapter = ADAPTER_FACTORY.createAdapter(null);
final SocketIoSocket socket1 = createDummySocket();
final SocketIoSocket socket2 = createDummySocket();
adapter.add("foo", socket1);
adapter.add("foo", socket2);
adapter.add("bar", socket2);
adapter.remove("foo", socket1);
assertEquals(1, adapter.listClients("foo").length);
assertEquals(1, adapter.listClients("bar").length);
adapter.remove("bar", socket1);
assertEquals(1, adapter.listClients("foo").length);
assertEquals(1, adapter.listClients("bar").length);
adapter.remove("foo", socket2);
assertEquals(0, adapter.listClients("foo").length);
assertEquals(1, adapter.listClients("bar").length);
}
@Test(expected = IllegalArgumentException.class)
public void test_listClients_exception_on_null_room() {
final SocketIoAdapter adapter = ADAPTER_FACTORY.createAdapter(null);
adapter.listClients(null);
}
@Test
public void test_listClients_unknown_room() {
final SocketIoAdapter adapter = ADAPTER_FACTORY.createAdapter(null);
assertEquals(0, adapter.listClients("foo").length);
assertEquals(0, adapter.listClients("bar").length);
}
@Test
public void test_listClients() {
final SocketIoAdapter adapter = ADAPTER_FACTORY.createAdapter(null);
final SocketIoSocket socket1 = createDummySocket();
final SocketIoSocket socket2 = createDummySocket();
final SocketIoSocket socket3 = createDummySocket();
adapter.add("foo", socket1);
assertEquals(1, adapter.listClients("foo").length);
adapter.add("foo", socket2);
assertEquals(2, adapter.listClients("foo").length);
adapter.add("foo", socket3);
assertEquals(3, adapter.listClients("foo").length);
adapter.remove("foo", socket1);
assertEquals(2, adapter.listClients("foo").length);
}
@Test(expected = IllegalArgumentException.class)
public void test_listClientRooms_exception_on_null_room() {
final SocketIoAdapter adapter = ADAPTER_FACTORY.createAdapter(null);
adapter.listClientRooms(null);
}
@Test
public void test_listClientRooms_unknown_socket() {
final SocketIoAdapter adapter = ADAPTER_FACTORY.createAdapter(null);
final SocketIoSocket socket1 = createDummySocket();
final SocketIoSocket socket2 = createDummySocket();
assertEquals(0, adapter.listClientRooms(socket1).length);
assertEquals(0, adapter.listClientRooms(socket2).length);
}
@Test
public void test_listClientRooms() {
final SocketIoAdapter adapter = ADAPTER_FACTORY.createAdapter(null);
final SocketIoSocket socket1 = createDummySocket();
final SocketIoSocket socket2 = createDummySocket();
adapter.add("foo", socket1);
assertEquals(1, adapter.listClientRooms(socket1).length);
adapter.add("bar", socket1);
assertEquals(2, adapter.listClientRooms(socket1).length);
adapter.add("foo", socket2);
assertEquals(1, adapter.listClientRooms(socket2).length);
adapter.add("baz", socket1);
assertEquals(3, adapter.listClientRooms(socket1).length);
adapter.add("baz", socket2);
assertEquals(2, adapter.listClientRooms(socket2).length);
}
@Test(expected = IllegalArgumentException.class)
public void test_broadcast_exception_on_null_packet() {
final SocketIoAdapter adapter = ADAPTER_FACTORY.createAdapter(null);
adapter.broadcast(null, null, null);
}
@Test
public void test_broadcast_all_rooms_no_exclusions() {
final HashMap connectedSockets = new HashMap<>();
final SocketIoNamespaceImpl namespace = Mockito.mock(SocketIoNamespaceImpl.class);
Mockito.doAnswer(invocationOnMock -> connectedSockets).when(namespace).getConnectedSockets();
final SocketIoAdapter adapter = ADAPTER_FACTORY.createAdapter(namespace);
final SocketIoSocket socket1 = createDummySocket();
adapter.add(socket1.getId(), socket1); // Adding to room named as socket id
final SocketIoSocket socket2 = createDummySocket();
adapter.add(socket2.getId(), socket2); // Adding to room named as socket id
final Packet> packet = new Packet<>(Parser.DISCONNECT);
connectedSockets.put(socket1.getId(), socket1);
adapter.broadcast(packet, null, null);
Mockito.verify(socket1, Mockito.times(1))
.sendPacket(Mockito.eq(packet));
connectedSockets.put(socket2.getId(), socket2);
adapter.broadcast(packet, null, null);
Mockito.verify(socket1, Mockito.times(2))
.sendPacket(Mockito.eq(packet));
Mockito.verify(socket2, Mockito.times(1))
.sendPacket(Mockito.eq(packet));
connectedSockets.remove(socket1.getId());
adapter.broadcast(packet, null, null);
Mockito.verify(socket1, Mockito.times(2))
.sendPacket(Mockito.eq(packet));
Mockito.verify(socket2, Mockito.times(2))
.sendPacket(Mockito.eq(packet));
}
@Test
public void test_broadcast_all_rooms_with_exclusions() {
final HashMap connectedSockets = new HashMap<>();
final SocketIoNamespaceImpl namespace = Mockito.mock(SocketIoNamespaceImpl.class);
Mockito.doAnswer(invocationOnMock -> connectedSockets).when(namespace).getConnectedSockets();
final SocketIoAdapter adapter = ADAPTER_FACTORY.createAdapter(namespace);
final SocketIoSocket socket1 = createDummySocket();
adapter.add(socket1.getId(), socket1); // Adding to room named as socket id
final SocketIoSocket socket2 = createDummySocket();
adapter.add(socket2.getId(), socket2); // Adding to room named as socket id
final Packet> packet = new Packet<>(Parser.DISCONNECT);
connectedSockets.put(socket1.getId(), socket1);
adapter.broadcast(packet, null, null);
Mockito.verify(socket1, Mockito.times(1))
.sendPacket(Mockito.eq(packet));
connectedSockets.put(socket2.getId(), socket2);
adapter.broadcast(packet, null, new String[] { socket1.getId() });
Mockito.verify(socket1, Mockito.times(1))
.sendPacket(Mockito.eq(packet));
Mockito.verify(socket2, Mockito.times(1))
.sendPacket(Mockito.eq(packet));
connectedSockets.remove(socket1.getId());
adapter.broadcast(packet, null, new String[] { socket2.getId() });
Mockito.verify(socket1, Mockito.times(1))
.sendPacket(Mockito.eq(packet));
Mockito.verify(socket2, Mockito.times(1))
.sendPacket(Mockito.eq(packet));
}
@Test
public void test_broadcast_one_room_no_exclusions() {
final HashMap connectedSockets = new HashMap<>();
final SocketIoNamespaceImpl namespace = Mockito.mock(SocketIoNamespaceImpl.class);
Mockito.doAnswer(invocationOnMock -> connectedSockets).when(namespace).getConnectedSockets();
final SocketIoAdapter adapter = ADAPTER_FACTORY.createAdapter(namespace);
final SocketIoSocket socket1 = createDummySocket();
adapter.add(socket1.getId(), socket1); // Adding to room named as socket id
final SocketIoSocket socket2 = createDummySocket();
adapter.add(socket2.getId(), socket2); // Adding to room named as socket id
final Packet> packet = new Packet<>(Parser.DISCONNECT);
connectedSockets.put(socket1.getId(), socket1);
connectedSockets.put(socket2.getId(), socket2);
adapter.add("foo", socket1);
adapter.broadcast(packet, new String[]{ "foo" }, null);
Mockito.verify(socket1, Mockito.times(1))
.sendPacket(Mockito.eq(packet));
Mockito.verify(socket2, Mockito.times(0))
.sendPacket(Mockito.eq(packet));
adapter.add("foo", socket2);
adapter.broadcast(packet, new String[]{ "foo" }, null);
Mockito.verify(socket1, Mockito.times(2))
.sendPacket(Mockito.eq(packet));
Mockito.verify(socket2, Mockito.times(1))
.sendPacket(Mockito.eq(packet));
adapter.remove("foo", socket1);
adapter.broadcast(packet, new String[]{ "foo" }, null);
Mockito.verify(socket1, Mockito.times(2))
.sendPacket(Mockito.eq(packet));
Mockito.verify(socket2, Mockito.times(2))
.sendPacket(Mockito.eq(packet));
}
@Test
public void test_broadcast_one_room_with_exclusions() {
final HashMap connectedSockets = new HashMap<>();
final SocketIoNamespaceImpl namespace = Mockito.mock(SocketIoNamespaceImpl.class);
Mockito.doAnswer(invocationOnMock -> connectedSockets).when(namespace).getConnectedSockets();
final SocketIoAdapter adapter = ADAPTER_FACTORY.createAdapter(namespace);
final SocketIoSocket socket1 = createDummySocket();
adapter.add(socket1.getId(), socket1); // Adding to room named as socket id
final SocketIoSocket socket2 = createDummySocket();
adapter.add(socket2.getId(), socket2); // Adding to room named as socket id
final Packet> packet = new Packet<>(Parser.DISCONNECT);
connectedSockets.put(socket1.getId(), socket1);
connectedSockets.put(socket2.getId(), socket2);
adapter.add("foo", socket1);
adapter.add("foo", socket2);
adapter.broadcast(packet, new String[]{ "foo" }, new String[]{ socket1.getId() });
Mockito.verify(socket1, Mockito.times(0))
.sendPacket(Mockito.eq(packet));
Mockito.verify(socket2, Mockito.times(1))
.sendPacket(Mockito.eq(packet));
adapter.broadcast(packet, new String[]{ "foo" }, new String[]{ socket2.getId() });
Mockito.verify(socket1, Mockito.times(1))
.sendPacket(Mockito.eq(packet));
Mockito.verify(socket2, Mockito.times(1))
.sendPacket(Mockito.eq(packet));
}
@Test
public void test_broadcast_multi_room_no_exclusions() {
final HashMap connectedSockets = new HashMap<>();
final SocketIoNamespaceImpl namespace = Mockito.mock(SocketIoNamespaceImpl.class);
Mockito.doAnswer(invocationOnMock -> connectedSockets).when(namespace).getConnectedSockets();
final SocketIoAdapter adapter = ADAPTER_FACTORY.createAdapter(namespace);
final SocketIoSocket socket1 = createDummySocket();
adapter.add(socket1.getId(), socket1); // Adding to room named as socket id
final SocketIoSocket socket2 = createDummySocket();
adapter.add(socket2.getId(), socket2); // Adding to room named as socket id
final Packet> packet = new Packet<>(Parser.DISCONNECT);
connectedSockets.put(socket1.getId(), socket1);
connectedSockets.put(socket2.getId(), socket2);
adapter.add("foo", socket1);
adapter.add("bar", socket1);
adapter.broadcast(packet, new String[]{ "foo", "bar" }, null);
Mockito.verify(socket1, Mockito.times(1))
.sendPacket(Mockito.eq(packet));
Mockito.verify(socket2, Mockito.times(0))
.sendPacket(Mockito.eq(packet));
adapter.add("bar", socket2);
adapter.broadcast(packet, new String[]{ "foo", "bar" }, null);
Mockito.verify(socket1, Mockito.times(2))
.sendPacket(Mockito.eq(packet));
Mockito.verify(socket2, Mockito.times(1))
.sendPacket(Mockito.eq(packet));
adapter.remove("foo", socket1);
adapter.broadcast(packet, new String[]{ "foo" }, null);
Mockito.verify(socket1, Mockito.times(2))
.sendPacket(Mockito.eq(packet));
Mockito.verify(socket2, Mockito.times(1))
.sendPacket(Mockito.eq(packet));
}
private SocketIoSocket createDummySocket() {
final String sid = ServerYeast.yeast();
final SocketIoSocket socket = Mockito.mock(SocketIoSocket.class);
Mockito.doAnswer(invocationOnMock -> sid).when(socket).getId();
return socket;
}
}
================================================
FILE: socket.io-server/src/test/java/io/socket/socketio/server/SocketIoNamespaceImplTest.java
================================================
package io.socket.socketio.server;
import io.socket.engineio.server.EngineIoServer;
import io.socket.socketio.server.parser.Packet;
import io.socket.socketio.server.parser.Parser;
import org.json.JSONArray;
import org.junit.Test;
import org.mockito.Mockito;
import static org.junit.Assert.*;
public final class SocketIoNamespaceImplTest {
@SuppressWarnings("ResultOfMethodCallIgnored")
@Test
public void test_constructor_new_instance() {
final SocketIoServer server = Mockito.spy(new SocketIoServer(new EngineIoServer()));
final SocketIoNamespaceImpl namespace = new SocketIoNamespaceImpl(server, "/_foo");
Mockito.verify(server, Mockito.times(1)).getAdapterFactory();
assertEquals("/_foo", namespace.getName());
assertEquals(server, namespace.getServer());
assertNotNull(namespace.getAdapter());
assertEquals(0, namespace.getConnectedSockets().size());
}
@Test
public void test_broadcast_all_rooms() {
final SocketIoAdapter adapter = Mockito.mock(SocketIoAdapter.class);
Mockito.doCallRealMethod().when(adapter)
.broadcast(Mockito.any(Packet.class), Mockito.isNull());
Mockito.doAnswer(invocationOnMock -> {
final Packet packet = invocationOnMock.getArgument(0);
final String[] rooms = invocationOnMock.getArgument(1);
final String[] socketsExcluded = invocationOnMock.getArgument(2);
assertEquals(Parser.EVENT, packet.type);
assertNotNull(packet.data);
assertEquals(JSONArray.class, packet.data.getClass());
assertEquals(1, ((JSONArray) packet.data).length());
assertEquals("foo", ((JSONArray) packet.data).get(0));
assertNull(rooms);
assertNull(socketsExcluded);
return null;
}).when(adapter).broadcast(Mockito.any(Packet.class), Mockito.isNull(), Mockito.isNull());
final SocketIoAdapter.AdapterFactory adapterFactory = Mockito.mock(SocketIoAdapter.AdapterFactory.class);
Mockito.doAnswer(invocationOnMock -> adapter)
.when(adapterFactory)
.createAdapter(Mockito.any(SocketIoNamespaceImpl.class));
final SocketIoServer server = Mockito.spy(new SocketIoServer(
new EngineIoServer(),
SocketIoServerOptions.newFromDefault()
.setAdapterFactory(adapterFactory)));
final SocketIoNamespace namespace = server.namespace("/");
namespace.broadcast(null, "foo");
Mockito.verify(adapter, Mockito.times(1))
.broadcast(Mockito.any(Packet.class), Mockito.isNull(), Mockito.isNull());
}
@Test
public void test_broadcast_one_room() {
final SocketIoAdapter adapter = Mockito.mock(SocketIoAdapter.class);
Mockito.doCallRealMethod().when(adapter)
.broadcast(Mockito.any(Packet.class), Mockito.any(String[].class));
Mockito.doAnswer(invocationOnMock -> {
final Packet packet = invocationOnMock.getArgument(0);
final String[] rooms = invocationOnMock.getArgument(1);
final String[] socketsExcluded = invocationOnMock.getArgument(2);
assertEquals(Parser.EVENT, packet.type);
assertNotNull(packet.data);
assertEquals(JSONArray.class, packet.data.getClass());
assertEquals(1, ((JSONArray) packet.data).length());
assertEquals("bar", ((JSONArray) packet.data).get(0));
assertNotNull(rooms);
assertEquals(1, rooms.length);
assertEquals("foo", rooms[0]);
assertNull(socketsExcluded);
return null;
}).when(adapter).broadcast(Mockito.any(Packet.class), Mockito.any(String[].class), Mockito.isNull());
final SocketIoAdapter.AdapterFactory adapterFactory = Mockito.mock(SocketIoAdapter.AdapterFactory.class);
Mockito.doAnswer(invocationOnMock -> adapter)
.when(adapterFactory)
.createAdapter(Mockito.any(SocketIoNamespaceImpl.class));
final SocketIoServer server = Mockito.spy(new SocketIoServer(
new EngineIoServer(),
SocketIoServerOptions.newFromDefault()
.setAdapterFactory(adapterFactory)));
final SocketIoNamespace namespace = server.namespace("/");
namespace.broadcast("foo", "bar");
Mockito.verify(adapter, Mockito.times(1))
.broadcast(Mockito.any(Packet.class), Mockito.any(String[].class), Mockito.isNull());
}
@Test
public void test_broadcast_multiple_rooms() {
final SocketIoAdapter adapter = Mockito.mock(SocketIoAdapter.class);
Mockito.doCallRealMethod().when(adapter)
.broadcast(Mockito.any(Packet.class), Mockito.any(String[].class));
Mockito.doAnswer(invocationOnMock -> {
final Packet packet = invocationOnMock.getArgument(0);
final String[] rooms = invocationOnMock.getArgument(1);
final String[] socketsExcluded = invocationOnMock.getArgument(2);
assertEquals(Parser.EVENT, packet.type);
assertNotNull(packet.data);
assertEquals(JSONArray.class, packet.data.getClass());
assertEquals(1, ((JSONArray) packet.data).length());
assertEquals("baz", ((JSONArray) packet.data).get(0));
assertNotNull(rooms);
assertArrayEquals(new String[] { "foo", "bar" }, rooms);
assertNull(socketsExcluded);
return null;
}).when(adapter).broadcast(Mockito.any(Packet.class), Mockito.any(String[].class), Mockito.isNull());
final SocketIoAdapter.AdapterFactory adapterFactory = Mockito.mock(SocketIoAdapter.AdapterFactory.class);
Mockito.doAnswer(invocationOnMock -> adapter)
.when(adapterFactory)
.createAdapter(Mockito.any(SocketIoNamespaceImpl.class));
final SocketIoServer server = Mockito.spy(new SocketIoServer(
new EngineIoServer(),
SocketIoServerOptions.newFromDefault()
.setAdapterFactory(adapterFactory)));
final SocketIoNamespace namespace = server.namespace("/");
namespace.broadcast(new String[] { "foo", "bar" }, "baz", null);
Mockito.verify(adapter, Mockito.times(1))
.broadcast(Mockito.any(Packet.class), Mockito.any(String[].class), Mockito.isNull());
}
}
================================================
FILE: socket.io-server/src/test/java/io/socket/socketio/server/SocketIoServerOptionsTest.java
================================================
package io.socket.socketio.server;
import org.junit.Test;
import org.mockito.Mockito;
import static org.junit.Assert.*;
public final class SocketIoServerOptionsTest {
@Test(expected = IllegalStateException.class)
public void testDefaultLocked_setAdapterFactory() {
SocketIoServerOptions.DEFAULT.setAdapterFactory(null);
}
@Test
public void test_setAdapterFactory() {
final SocketIoServerOptions options = SocketIoServerOptions.newFromDefault();
options.setAdapterFactory(Mockito.mock(SocketIoAdapter.AdapterFactory.class));
assertNotNull(options.getAdapterFactory());
assertTrue(options.getAdapterFactory() instanceof SocketIoMemoryAdapter.AdapterFactory);
}
@Test
public void test_setAdapterFactory_null() {
final SocketIoServerOptions options = SocketIoServerOptions.newFromDefault();
options.setAdapterFactory(null);
assertNotNull(options.getAdapterFactory());
assertTrue(options.getAdapterFactory() instanceof SocketIoMemoryAdapter.AdapterFactory);
}
@Test(expected = IllegalStateException.class)
public void test_lock() {
final SocketIoServerOptions options = SocketIoServerOptions.newFromDefault();
options.setAdapterFactory(Mockito.mock(SocketIoAdapter.AdapterFactory.class));
options.lock();
options.setAdapterFactory(null);
}
}
================================================
FILE: socket.io-server/src/test/java/io/socket/socketio/server/SocketIoServerTest.java
================================================
package io.socket.socketio.server;
import io.socket.engineio.server.Emitter;
import io.socket.engineio.server.EngineIoServer;
import io.socket.socketio.server.parser.IOParser;
import org.junit.Test;
import org.mockito.Mockito;
import java.util.regex.Pattern;
import static org.junit.Assert.*;
public final class SocketIoServerTest {
@Test
public void test_constructor() {
final EngineIoServer engineIoServer = Mockito.spy(new EngineIoServer());
final SocketIoServer server = new SocketIoServer(engineIoServer);
// Verify 'connection' event bind
Mockito.verify(engineIoServer, Mockito.times(1))
.on(Mockito.eq("connection"), Mockito.any(Emitter.Listener.class));
// Verify encoder
assertNotNull(server.getEncoder());
assertEquals(IOParser.Encoder.class, server.getEncoder().getClass());
// Verify adapter factory
assertNotNull(server.getAdapterFactory());
assertEquals(SocketIoMemoryAdapter.Factory.class, server.getAdapterFactory().getClass());
// Verify default namespace '/'
assertTrue(server.hasNamespace("/"));
}
@Test
public void test_namespace_new() {
final SocketIoServer server = new SocketIoServer(new EngineIoServer());
// With slash
assertFalse(server.hasNamespace("/foo"));
server.namespace("/foo");
assertTrue(server.hasNamespace("/foo"));
assertTrue(server.hasNamespace("foo"));
// Without slash
assertFalse(server.hasNamespace("bar"));
server.namespace("bar");
assertTrue(server.hasNamespace("/bar"));
assertTrue(server.hasNamespace("bar"));
}
@Test
public void test_namespaceGroup_new() {
final SocketIoServer server = new SocketIoServer(new EngineIoServer());
server.namespace(Pattern.compile("^/foo[0-9]$"));
assertFalse(server.checkNamespace("foo"));
assertTrue(server.checkNamespace("foo1"));
assertFalse(server.checkNamespace("foo10"));
assertFalse(server.checkNamespace("foo1a"));
assertFalse(server.checkNamespace("bar"));
server.namespace(namespace -> {
switch (namespace) {
case "/bar":
return true;
case "/baz":
return true;
}
return false;
});
assertTrue(server.checkNamespace("bar"));
assertTrue(server.checkNamespace("baz"));
assertFalse(server.checkNamespace("foobaz"));
}
@Test
public void test_connection() {
final EngineIoServer engineIoServer = new EngineIoServer();
final SocketIoServer server = new SocketIoServer(engineIoServer);
final SocketIoNamespace namespace = server.namespace("/");
assertNotNull(namespace);
final Emitter.Listener connectionListener = Mockito.mock(Emitter.Listener.class);
namespace.on("connect", connectionListener);
namespace.on("connection", connectionListener);
final StubEngineIoWebSocket webSocket = new StubEngineIoWebSocket();
engineIoServer.handleWebSocket(webSocket);
webSocket.emitConnect(null);
Mockito.verify(connectionListener, Mockito.times(2))
.call(Mockito.any(SocketIoSocket.class));
}
@Test
public void test_disconnect() {
final EngineIoServer engineIoServer = new EngineIoServer();
final SocketIoServer server = new SocketIoServer(engineIoServer);
final SocketIoNamespace namespace = server.namespace("/");
assertNotNull(namespace);
final Emitter.Listener disconnectListener = Mockito.mock(Emitter.Listener.class);
final Emitter.Listener connectionListener = Mockito.mock(Emitter.Listener.class);
Mockito.doAnswer(invocation -> {
final Object[] args = invocation.getArguments();
final SocketIoSocket socket = (SocketIoSocket) args[0];
socket.on("disconnect", disconnectListener);
return null;
}).when(connectionListener).call(Mockito.any());
namespace.on("connect", connectionListener);
final StubEngineIoWebSocket webSocket = new StubEngineIoWebSocket();
engineIoServer.handleWebSocket(webSocket);
webSocket.emitConnect(null);
Mockito.verify(connectionListener, Mockito.times(1))
.call(Mockito.any(SocketIoSocket.class));
webSocket.emit("close", "client close", null);
Mockito.verify(disconnectListener, Mockito.times(1))
.call(Mockito.anyString());
}
}
================================================
FILE: socket.io-server/src/test/java/io/socket/socketio/server/SocketIoSocketTest.java
================================================
package io.socket.socketio.server;
import io.socket.engineio.server.Emitter;
import io.socket.engineio.server.EngineIoServer;
import io.socket.engineio.server.EngineIoSocket;
import io.socket.socketio.server.parser.Packet;
import io.socket.socketio.server.parser.Parser;
import org.json.JSONArray;
import org.junit.Test;
import org.mockito.Mockito;
import static org.junit.Assert.*;
public final class SocketIoSocketTest {
private static final class LocalAdapterFactory implements SocketIoAdapter.AdapterFactory {
SocketIoAdapter adapter;
@Override
public SocketIoAdapter createAdapter(SocketIoNamespace namespace) {
if (adapter == null) {
adapter = Mockito.spy(new SocketIoAdapterImpl(namespace));
}
return adapter;
}
}
@Test
public void test_broadcast_all_rooms() {
final LocalAdapterFactory adapterFactory = new LocalAdapterFactory();
final EngineIoServer engineIoServer = new EngineIoServer();
final SocketIoServer server = Mockito.spy(new SocketIoServer(
engineIoServer,
SocketIoServerOptions.newFromDefault().setAdapterFactory(adapterFactory)));
final SocketIoNamespace namespace = server.namespace("/");
final Emitter.Listener connectionListener = Mockito.mock(Emitter.Listener.class);
Mockito.doAnswer(invocation -> {
final Object[] args = invocation.getArguments();
final SocketIoSocket socket = (SocketIoSocket) args[0];
socket.broadcast(null, "foo");
Mockito.verify(adapterFactory.adapter, Mockito.times(1))
.broadcast(Mockito.any(Packet.class), Mockito.isNull(), Mockito.eq(new String[] {
socket.getId()
}));
return null;
}).when(connectionListener).call(Mockito.any());
namespace.on("connection", connectionListener);
final StubEngineIoWebSocket webSocket = new StubEngineIoWebSocket();
engineIoServer.handleWebSocket(webSocket);
webSocket.emitConnect(null);
Mockito.verify(connectionListener, Mockito.times(1))
.call(Mockito.any(SocketIoSocket.class));
}
@Test
public void test_broadcast_one_rooms() {
final LocalAdapterFactory adapterFactory = new LocalAdapterFactory();
final EngineIoServer engineIoServer = new EngineIoServer();
final SocketIoServer server = Mockito.spy(new SocketIoServer(
engineIoServer,
SocketIoServerOptions.newFromDefault().setAdapterFactory(adapterFactory)));
final SocketIoNamespace namespace = server.namespace("/");
final Emitter.Listener connectionListener = Mockito.mock(Emitter.Listener.class);
Mockito.doAnswer(invocation -> {
final Object[] args = invocation.getArguments();
final SocketIoSocket socket = (SocketIoSocket) args[0];
socket.broadcast("foo_room", "foo");
Mockito.verify(adapterFactory.adapter, Mockito.times(1))
.broadcast(Mockito.any(Packet.class), Mockito.eq(new String[]{
"foo_room"
}), Mockito.eq(new String[] {
socket.getId()
}));
return null;
}).when(connectionListener).call(Mockito.any());
namespace.on("connection", connectionListener);
final StubEngineIoWebSocket webSocket = new StubEngineIoWebSocket();
engineIoServer.handleWebSocket(webSocket);
webSocket.emitConnect(null);
Mockito.verify(connectionListener, Mockito.times(1))
.call(Mockito.any(SocketIoSocket.class));
}
@Test
public void test_broadcast_multiple_rooms() {
final LocalAdapterFactory adapterFactory = new LocalAdapterFactory();
final EngineIoServer engineIoServer = new EngineIoServer();
final SocketIoServer server = Mockito.spy(new SocketIoServer(
engineIoServer,
SocketIoServerOptions.newFromDefault().setAdapterFactory(adapterFactory)));
final SocketIoNamespace namespace = server.namespace("/");
final Emitter.Listener connectionListener = Mockito.mock(Emitter.Listener.class);
Mockito.doAnswer(invocation -> {
final Object[] args = invocation.getArguments();
final SocketIoSocket socket = (SocketIoSocket) args[0];
final String[] rooms = new String[] {
"foo_room", "bar_room"
};
socket.broadcast(rooms, "foo", null);
Mockito.verify(adapterFactory.adapter, Mockito.times(1))
.broadcast(Mockito.any(Packet.class), Mockito.eq(rooms), Mockito.eq(new String[] {
socket.getId()
}));
return null;
}).when(connectionListener).call(Mockito.any());
namespace.on("connection", connectionListener);
final StubEngineIoWebSocket webSocket = new StubEngineIoWebSocket();
engineIoServer.handleWebSocket(webSocket);
webSocket.emitConnect(null);
Mockito.verify(connectionListener, Mockito.times(1))
.call(Mockito.any(SocketIoSocket.class));
}
@Test
public void test_joinRoom() {
final LocalAdapterFactory adapterFactory = new LocalAdapterFactory();
final EngineIoServer engineIoServer = new EngineIoServer();
final SocketIoServer server = Mockito.spy(new SocketIoServer(
engineIoServer,
SocketIoServerOptions.newFromDefault().setAdapterFactory(adapterFactory)));
final SocketIoNamespace namespace = server.namespace("/");
final Emitter.Listener connectionListener = Mockito.mock(Emitter.Listener.class);
Mockito.doAnswer(invocation -> {
final Object[] args = invocation.getArguments();
final SocketIoSocket socket = (SocketIoSocket) args[0];
final SocketIoAdapter adapter = adapterFactory.adapter;
// Join foo
Mockito.reset(adapter);
socket.joinRoom("foo");
Mockito.verify(adapter, Mockito.times(1))
.add(Mockito.eq("foo"), Mockito.eq(socket));
// Join bar
Mockito.reset(adapter);
socket.joinRoom("bar");
Mockito.verify(adapter, Mockito.times(1))
.add(Mockito.eq("bar"), Mockito.eq(socket));
// Duplicate join foo
Mockito.reset(adapter);
socket.joinRoom("foo");
Mockito.verify(adapter, Mockito.times(0))
.add(Mockito.eq("foo"), Mockito.eq(socket));
return null;
}).when(connectionListener).call(Mockito.any());
namespace.on("connection", connectionListener);
final StubEngineIoWebSocket webSocket = new StubEngineIoWebSocket();
engineIoServer.handleWebSocket(webSocket);
webSocket.emitConnect(null);
Mockito.verify(connectionListener, Mockito.times(1))
.call(Mockito.any(SocketIoSocket.class));
}
@Test
public void test_leaveRoom() {
final LocalAdapterFactory adapterFactory = new LocalAdapterFactory();
final EngineIoServer engineIoServer = new EngineIoServer();
final SocketIoServer server = Mockito.spy(new SocketIoServer(
engineIoServer,
SocketIoServerOptions.newFromDefault().setAdapterFactory(adapterFactory)));
final SocketIoNamespace namespace = server.namespace("/");
final Emitter.Listener connectionListener = Mockito.mock(Emitter.Listener.class);
Mockito.doAnswer(invocation -> {
final Object[] args = invocation.getArguments();
final SocketIoSocket socket = (SocketIoSocket) args[0];
final SocketIoAdapter adapter = adapterFactory.adapter;
// Join then leave
Mockito.reset(adapter);
socket.joinRoom("foo");
socket.leaveRoom("foo");
Mockito.verify(adapter, Mockito.times(1))
.remove(Mockito.eq("foo"), Mockito.eq(socket));
// Leave without join
Mockito.reset(adapter);
socket.leaveRoom("bar");
Mockito.verify(adapter, Mockito.times(0))
.remove(Mockito.eq("bar"), Mockito.eq(socket));
return null;
}).when(connectionListener).call(Mockito.any());
namespace.on("connection", connectionListener);
final StubEngineIoWebSocket webSocket = new StubEngineIoWebSocket();
engineIoServer.handleWebSocket(webSocket);
webSocket.emitConnect(null);
Mockito.verify(connectionListener, Mockito.times(1))
.call(Mockito.any(SocketIoSocket.class));
}
@Test
public void test_leaveAllRooms() {
final LocalAdapterFactory adapterFactory = new LocalAdapterFactory();
final EngineIoServer engineIoServer = new EngineIoServer();
final SocketIoServer server = Mockito.spy(new SocketIoServer(
engineIoServer,
SocketIoServerOptions.newFromDefault().setAdapterFactory(adapterFactory)));
final SocketIoNamespace namespace = server.namespace("/");
final Emitter.Listener connectionListener = Mockito.mock(Emitter.Listener.class);
Mockito.doAnswer(invocation -> {
final Object[] args = invocation.getArguments();
final SocketIoSocket socket = (SocketIoSocket) args[0];
final SocketIoAdapter adapter = adapterFactory.adapter;
Mockito.reset(adapter);
socket.joinRoom("foo", "bar");
socket.leaveAllRooms();
Mockito.verify(adapter, Mockito.times(1))
.remove(Mockito.eq("foo"), Mockito.eq(socket));
Mockito.verify(adapter, Mockito.times(1))
.remove(Mockito.eq("bar"), Mockito.eq(socket));
return null;
}).when(connectionListener).call(Mockito.any());
namespace.on("connection", connectionListener);
final StubEngineIoWebSocket webSocket = new StubEngineIoWebSocket();
engineIoServer.handleWebSocket(webSocket);
webSocket.emitConnect(null);
Mockito.verify(connectionListener, Mockito.times(1))
.call(Mockito.any(SocketIoSocket.class));
}
@Test
public void test_send_without_ack() {
final EngineIoServer engineIoServer = new EngineIoServer();
final SocketIoServer server = new SocketIoServer(engineIoServer);
final SocketIoNamespaceImpl namespace = (SocketIoNamespaceImpl)server.namespace("/");
final Emitter.Listener connectionListener = Mockito.mock(Emitter.Listener.class);
Mockito.doAnswer(invocation -> {
final Object[] args = invocation.getArguments();
final SocketIoClient client = Mockito.spy(new SocketIoClient(server, (EngineIoSocket) args[0]));
final SocketIoSocket socket = new SocketIoSocket(namespace, client, null);
Mockito.doAnswer(invocationOnMock -> {
final Packet> packet = invocationOnMock.getArgument(0);
assertNotNull(packet);
assertEquals(Parser.EVENT, packet.type);
assertEquals(-1, packet.id);
assertNotNull(packet.data);
assertEquals(JSONArray.class, packet.data.getClass());
return null;
}).when(client).sendPacket(Mockito.any(Packet.class));
socket.send("foo");
Mockito.verify(client, Mockito.times(1))
.sendPacket(Mockito.any(Packet.class));
return null;
}).when(connectionListener).call(Mockito.any());
engineIoServer.on("connection", connectionListener);
final StubEngineIoWebSocket webSocket = new StubEngineIoWebSocket();
engineIoServer.handleWebSocket(webSocket);
webSocket.emitConnect(null);
Mockito.verify(connectionListener, Mockito.times(1))
.call(Mockito.any(EngineIoSocket.class));
}
@Test
public void test_send_with_ack() {
final EngineIoServer engineIoServer = new EngineIoServer();
final SocketIoServer server = new SocketIoServer(engineIoServer);
final SocketIoNamespaceImpl namespace = (SocketIoNamespaceImpl)server.namespace("/");
final Emitter.Listener connectionListener = Mockito.mock(Emitter.Listener.class);
Mockito.doAnswer(invocation -> {
final Object[] args = invocation.getArguments();
final SocketIoClient client = Mockito.spy(new SocketIoClient(server, (EngineIoSocket) args[0]));
final SocketIoSocket socket = new SocketIoSocket(namespace, client, null);
Mockito.doAnswer(invocationOnMock -> {
final Packet> packet = invocationOnMock.getArgument(0);
assertNotNull(packet);
assertEquals(Parser.EVENT, packet.type);
assertNotEquals(-1, packet.id);
assertNotNull(packet.data);
assertEquals(JSONArray.class, packet.data.getClass());
return null;
}).when(client).sendPacket(Mockito.any(Packet.class));
final SocketIoSocket.ReceivedByRemoteAcknowledgementCallback acknowledgementCallback = args1 -> { };
socket.send("foo", null, acknowledgementCallback);
Mockito.verify(client, Mockito.times(1))
.sendPacket(Mockito.any(Packet.class));
return null;
}).when(connectionListener).call(Mockito.any());
engineIoServer.on("connection", connectionListener);
final StubEngineIoWebSocket webSocket = new StubEngineIoWebSocket();
engineIoServer.handleWebSocket(webSocket);
webSocket.emitConnect(null);
Mockito.verify(connectionListener, Mockito.times(1))
.call(Mockito.any(EngineIoSocket.class));
}
@Test
public void test_disconnect_with_close() {
final EngineIoServer engineIoServer = new EngineIoServer();
final SocketIoServer server = new SocketIoServer(engineIoServer);
final SocketIoNamespaceImpl namespace = (SocketIoNamespaceImpl)server.namespace("/");
final Emitter.Listener connectionListener = Mockito.mock(Emitter.Listener.class);
Mockito.doAnswer(invocation -> {
final Object[] args = invocation.getArguments();
final SocketIoClient client = Mockito.spy(new SocketIoClient(server, (EngineIoSocket) args[0]));
final SocketIoSocket socket = new SocketIoSocket(namespace, client, null);
socket.disconnect(true);
Mockito.verify(client, Mockito.times(1))
.disconnect();
return null;
}).when(connectionListener).call(Mockito.any());
engineIoServer.on("connection", connectionListener);
final StubEngineIoWebSocket webSocket = new StubEngineIoWebSocket();
engineIoServer.handleWebSocket(webSocket);
webSocket.emitConnect(null);
Mockito.verify(connectionListener, Mockito.times(1))
.call(Mockito.any(EngineIoSocket.class));
}
@Test
public void test_disconnect_without_close() {
final EngineIoServer engineIoServer = new EngineIoServer();
final SocketIoServer server = new SocketIoServer(engineIoServer);
final SocketIoNamespaceImpl namespace = (SocketIoNamespaceImpl)server.namespace("/");
final Emitter.Listener connectionListener = Mockito.mock(Emitter.Listener.class);
Mockito.doAnswer(invocation -> {
final Object[] args = invocation.getArguments();
final SocketIoClient client = Mockito.spy(new SocketIoClient(server, (EngineIoSocket) args[0]));
final SocketIoSocket socket = new SocketIoSocket(namespace, client, null);
Mockito.doAnswer(invocationOnMock -> {
final Packet> packet = invocationOnMock.getArgument(0);
assertNotNull(packet);
assertEquals(Parser.DISCONNECT, packet.type);
return null;
}).when(client).sendPacket(Mockito.any(Packet.class));
socket.disconnect(false);
Mockito.verify(client).sendPacket(Mockito.any(Packet.class));
return null;
}).when(connectionListener).call(Mockito.any());
engineIoServer.on("connection", connectionListener);
final StubEngineIoWebSocket webSocket = new StubEngineIoWebSocket();
engineIoServer.handleWebSocket(webSocket);
webSocket.emitConnect(null);
Mockito.verify(connectionListener, Mockito.times(1))
.call(Mockito.any(EngineIoSocket.class));
}
@Test
public void test_sendPacket() {
final EngineIoServer engineIoServer = new EngineIoServer();
final SocketIoServer server = new SocketIoServer(engineIoServer);
final SocketIoNamespaceImpl namespace = (SocketIoNamespaceImpl)server.namespace("/");
final Emitter.Listener connectionListener = Mockito.mock(Emitter.Listener.class);
Mockito.doAnswer(invocation -> {
final Object[] args = invocation.getArguments();
final SocketIoClient client = Mockito.spy(new SocketIoClient(server, (EngineIoSocket) args[0]));
final SocketIoSocket socket = new SocketIoSocket(namespace, client, null);
Mockito.doAnswer(invocationOnMock -> {
final Packet> packet = invocationOnMock.getArgument(0);
assertNotNull(packet);
assertEquals("/", packet.nsp);
return null;
}).when(client).sendPacket(Mockito.any(Packet.class));
socket.sendPacket(new Packet<>(Parser.CONNECT));
Mockito.verify(client, Mockito.times(1))
.sendPacket(Mockito.any(Packet.class));
return null;
}).when(connectionListener).call(Mockito.any());
engineIoServer.on("connection", connectionListener);
final StubEngineIoWebSocket webSocket = new StubEngineIoWebSocket();
engineIoServer.handleWebSocket(webSocket);
webSocket.emitConnect(null);
Mockito.verify(connectionListener, Mockito.times(1))
.call(Mockito.any(EngineIoSocket.class));
}
@Test
public void test_onConnect() {
final EngineIoServer engineIoServer = new EngineIoServer();
final SocketIoServer server = Mockito.spy(new SocketIoServer(engineIoServer));
final SocketIoNamespaceImpl namespace = (SocketIoNamespaceImpl)server.namespace("/foo");
final Emitter.Listener connectionListener = Mockito.mock(Emitter.Listener.class);
Mockito.doAnswer(invocation -> {
final Object[] args = invocation.getArguments();
final EngineIoSocket engineIoSocket = (EngineIoSocket) args[0];
final SocketIoClient client = Mockito.spy(new SocketIoClient(server, engineIoSocket));
Mockito.doAnswer(invocationOnMock -> {
final Packet> packet = invocationOnMock.getArgument(0);
assertNotNull(packet);
assertEquals(namespace.getName(), packet.nsp);
return null;
}).when(client).sendPacket(Mockito.any(Packet.class));
final SocketIoSocket socket = new SocketIoSocket(namespace, client, null);
socket.onConnect();
Mockito.verify(client, Mockito.times(1))
.sendPacket(Mockito.any(Packet.class));
return null;
}).when(connectionListener).call(Mockito.any());
engineIoServer.on("connection", connectionListener);
final StubEngineIoWebSocket webSocket = new StubEngineIoWebSocket();
engineIoServer.handleWebSocket(webSocket);
webSocket.emitConnect(null);
Mockito.verify(connectionListener, Mockito.times(1))
.call(Mockito.any(EngineIoSocket.class));
}
@Test
public void test_onDisconnect() {
final EngineIoServer engineIoServer = new EngineIoServer();
final SocketIoServer server = Mockito.spy(new SocketIoServer(engineIoServer));
final SocketIoNamespace namespace = server.namespace("/");
final Emitter.Listener connectionListener = Mockito.mock(Emitter.Listener.class);
Mockito.doAnswer(invocation -> {
final Object[] args = invocation.getArguments();
final SocketIoSocket socket = (SocketIoSocket) args[0];
final Emitter.Listener disconnectListener = Mockito.mock(Emitter.Listener.class);
socket.on("disconnect", disconnectListener);
Mockito.reset(disconnectListener);
socket.onDisconnect();
Mockito.verify(disconnectListener, Mockito.times(1))
.call(Mockito.anyString());
Mockito.reset(disconnectListener);
socket.onDisconnect();
Mockito.verify(disconnectListener, Mockito.times(0))
.call(Mockito.anyString());
return null;
}).when(connectionListener).call(Mockito.any());
namespace.on("connection", connectionListener);
final StubEngineIoWebSocket webSocket = new StubEngineIoWebSocket();
engineIoServer.handleWebSocket(webSocket);
webSocket.emitConnect(null);
Mockito.verify(connectionListener, Mockito.times(1))
.call(Mockito.any(SocketIoSocket.class));
}
@Test
public void test_onClose() {
final EngineIoServer engineIoServer = new EngineIoServer();
final SocketIoServer server = Mockito.spy(new SocketIoServer(engineIoServer));
final SocketIoNamespace namespace = server.namespace("/");
final Emitter.Listener connectionListener = Mockito.mock(Emitter.Listener.class);
Mockito.doAnswer(invocation -> {
final Object[] args = invocation.getArguments();
final SocketIoSocket socket = (SocketIoSocket) args[0];
final Emitter.Listener disconnectListener = Mockito.mock(Emitter.Listener.class);
socket.on("disconnect", disconnectListener);
Mockito.reset(disconnectListener);
socket.onClose("foo");
Mockito.verify(disconnectListener, Mockito.times(1))
.call(Mockito.eq("foo"));
Mockito.reset(disconnectListener);
socket.onClose("bar");
Mockito.verify(disconnectListener, Mockito.times(0))
.call(Mockito.anyString());
return null;
}).when(connectionListener).call(Mockito.any());
namespace.on("connection", connectionListener);
final StubEngineIoWebSocket webSocket = new StubEngineIoWebSocket();
engineIoServer.handleWebSocket(webSocket);
webSocket.emitConnect(null);
Mockito.verify(connectionListener, Mockito.times(1))
.call(Mockito.any(SocketIoSocket.class));
}
@Test
public void test_onError() {
final EngineIoServer engineIoServer = new EngineIoServer();
final SocketIoServer server = Mockito.spy(new SocketIoServer(engineIoServer));
final SocketIoNamespace namespace = server.namespace("/");
final Emitter.Listener connectionListener = Mockito.mock(Emitter.Listener.class);
Mockito.doAnswer(invocation -> {
final Object[] args = invocation.getArguments();
final SocketIoSocket socket = (SocketIoSocket) args[0];
final Emitter.Listener errorListener = Mockito.mock(Emitter.Listener.class);
socket.on("error", errorListener);
Mockito.reset(errorListener);
socket.onError("reason");
Mockito.verify(errorListener, Mockito.times(1))
.call(Mockito.eq("reason"));
return null;
}).when(connectionListener).call(Mockito.any());
namespace.on("connection", connectionListener);
final StubEngineIoWebSocket webSocket = new StubEngineIoWebSocket();
engineIoServer.handleWebSocket(webSocket);
webSocket.emitConnect(null);
Mockito.verify(connectionListener, Mockito.times(1))
.call(Mockito.any(SocketIoSocket.class));
}
@Test
public void test_onEvent_without_ack() {
final EngineIoServer engineIoServer = new EngineIoServer();
final SocketIoServer server = Mockito.spy(new SocketIoServer(engineIoServer));
final SocketIoNamespace namespace = server.namespace("/");
final Emitter.Listener connectionListener = Mockito.mock(Emitter.Listener.class);
Mockito.doAnswer(invocation -> {
final Object[] args = invocation.getArguments();
final SocketIoSocket socket = (SocketIoSocket) args[0];
final JSONArray data = new JSONArray();
data.put("foo");
data.put("bar");
final Packet eventPacket = new Packet<>(Parser.EVENT, data);
final Emitter.Listener messageListener = Mockito.mock(Emitter.Listener.class);
socket.on("foo", messageListener);
socket.onEvent(eventPacket);
Mockito.verify(messageListener, Mockito.times(1))
.call(Mockito.eq("bar"));
return null;
}).when(connectionListener).call(Mockito.any());
namespace.on("connection", connectionListener);
final StubEngineIoWebSocket webSocket = new StubEngineIoWebSocket();
engineIoServer.handleWebSocket(webSocket);
webSocket.emitConnect(null);
Mockito.verify(connectionListener, Mockito.times(1))
.call(Mockito.any(SocketIoSocket.class));
}
@Test
public void test_onEvent_with_ack() {
final EngineIoServer engineIoServer = new EngineIoServer();
final SocketIoServer server = Mockito.spy(new SocketIoServer(engineIoServer));
final SocketIoNamespaceImpl namespace = (SocketIoNamespaceImpl)server.namespace("/foo");
final Emitter.Listener connectionListener = Mockito.mock(Emitter.Listener.class);
Mockito.doAnswer(invocation -> {
final Object[] args = invocation.getArguments();
final EngineIoSocket engineIoSocket = (EngineIoSocket) args[0];
final SocketIoClient client = Mockito.spy(new SocketIoClient(server, engineIoSocket));
final JSONArray data = new JSONArray();
data.put("foo");
data.put("bar");
final Packet eventPacket = new Packet<>(Parser.EVENT, data);
eventPacket.id = namespace.nextId();
Mockito.doAnswer(invocationOnMock -> {
final Packet> packet = invocationOnMock.getArgument(0);
assertEquals(Parser.ACK, packet.type);
assertEquals(eventPacket.id, packet.id);
return null;
}).when(client).sendPacket(Mockito.any(Packet.class));
final SocketIoSocket socket = new SocketIoSocket(namespace, client, null);
final Emitter.Listener messageListener = Mockito.mock(Emitter.Listener.class);
Mockito.doAnswer(invocation1 -> {
final Object[] args1 = invocation1.getArguments();
((SocketIoSocket.ReceivedByLocalAcknowledgementCallback) args1[1]).sendAcknowledgement();
return null;
}).when(messageListener).call(Mockito.any(Object[].class));
socket.on("foo", messageListener);
socket.onEvent(eventPacket);
Mockito.verify(messageListener, Mockito.times(1))
.call(Mockito.eq("bar"), Mockito.any(SocketIoSocket.ReceivedByLocalAcknowledgementCallback.class));
Mockito.verify(client, Mockito.times(1))
.sendPacket(Mockito.any(Packet.class));
return null;
}).when(connectionListener).call(Mockito.any());
engineIoServer.on("connection", connectionListener);
final StubEngineIoWebSocket webSocket = new StubEngineIoWebSocket();
engineIoServer.handleWebSocket(webSocket);
webSocket.emitConnect(null);
Mockito.verify(connectionListener, Mockito.times(1))
.call(Mockito.any(EngineIoSocket.class));
}
@Test
public void test_onEvent_all_listener() {
final EngineIoServer engineIoServer = new EngineIoServer();
final SocketIoServer server = Mockito.spy(new SocketIoServer(engineIoServer));
final SocketIoNamespace namespace = server.namespace("/");
final SocketIoSocket.AllEventListener allEventListener = Mockito.mock(SocketIoSocket.AllEventListener.class);
final Emitter.Listener connectionListener = Mockito.mock(Emitter.Listener.class);
Mockito.doAnswer(invocation -> {
final Object[] args = invocation.getArguments();
final SocketIoSocket socket = (SocketIoSocket) args[0];
socket.registerAllEventListener(allEventListener);
{
final JSONArray data = new JSONArray();
data.put("foo");
final Packet eventPacket = new Packet<>(Parser.EVENT, data);
final Emitter.Listener messageListener = Mockito.mock(Emitter.Listener.class);
socket.on("foo", messageListener);
socket.onEvent(eventPacket);
Mockito.verify(messageListener, Mockito.times(1)).call();
}
{
final JSONArray data = new JSONArray();
data.put("bar");
final Packet eventPacket = new Packet<>(Parser.EVENT, data);
final Emitter.Listener messageListener = Mockito.mock(Emitter.Listener.class);
socket.on("bar", messageListener);
socket.onEvent(eventPacket);
Mockito.verify(messageListener, Mockito.times(1)).call();
}
return null;
}).when(connectionListener).call(Mockito.any());
namespace.on("connection", connectionListener);
final StubEngineIoWebSocket webSocket = new StubEngineIoWebSocket();
engineIoServer.handleWebSocket(webSocket);
webSocket.emitConnect(null);
Mockito.verify(connectionListener, Mockito.times(1))
.call(Mockito.any(SocketIoSocket.class));
Mockito.verify(allEventListener, Mockito.times(1))
.event(Mockito.eq("foo"));
Mockito.verify(allEventListener, Mockito.times(1))
.event(Mockito.eq("bar"));
}
@Test
public void test_onAck() {
class PacketId {
private int id;
}
final EngineIoServer engineIoServer = new EngineIoServer();
final SocketIoServer server = Mockito.spy(new SocketIoServer(engineIoServer));
final SocketIoNamespaceImpl namespace = (SocketIoNamespaceImpl)server.namespace("/foo");
final Emitter.Listener connectionListener = Mockito.mock(Emitter.Listener.class);
Mockito.doAnswer(invocation -> {
final Object[] args = invocation.getArguments();
final PacketId packetId = new PacketId();
final EngineIoSocket engineIoSocket = (EngineIoSocket) args[0];
final SocketIoClient client = Mockito.spy(new SocketIoClient(server, engineIoSocket));
Mockito.reset(client);
Mockito.doAnswer(invocationOnMock -> {
final Packet> packet = invocationOnMock.getArgument(0);
if (packet.type == Parser.EVENT) {
packetId.id = packet.id;
}
return null;
}).when(client).sendPacket(Mockito.any(Packet.class));
final SocketIoSocket socket = new SocketIoSocket(namespace, client, null);
final SocketIoSocket.ReceivedByRemoteAcknowledgementCallback callback = Mockito.mock(SocketIoSocket.ReceivedByRemoteAcknowledgementCallback.class);
socket.send("foo", null, callback);
Mockito.verify(client, Mockito.times(1))
.sendPacket(Mockito.any(Packet.class));
final Packet> ackPacket = new Packet<>(Parser.ACK);
ackPacket.id = packetId.id;
socket.onAck(ackPacket);
Mockito.verify(callback, Mockito.times(1))
.onReceivedByRemote();
return null;
}).when(connectionListener).call(Mockito.any());
engineIoServer.on("connection", connectionListener);
final StubEngineIoWebSocket webSocket = new StubEngineIoWebSocket();
engineIoServer.handleWebSocket(webSocket);
webSocket.emitConnect(null);
Mockito.verify(connectionListener, Mockito.times(1))
.call(Mockito.any(EngineIoSocket.class));
}
}
================================================
FILE: socket.io-server/src/test/java/io/socket/socketio/server/StubEngineIoWebSocket.java
================================================
package io.socket.socketio.server;
import io.socket.engineio.server.EngineIoWebSocket;
import io.socket.socketio.server.parser.IOParser;
import io.socket.socketio.server.parser.Packet;
import io.socket.socketio.server.parser.Parser;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public final class StubEngineIoWebSocket extends EngineIoWebSocket {
private final Map mQuery = new HashMap<>();
StubEngineIoWebSocket() {
this(4);
}
StubEngineIoWebSocket(int version) {
mQuery.put("EIO", Integer.toString(version, 10));
}
@Override
public Map getQuery() {
return mQuery;
}
@Override
public Map> getConnectionHeaders() {
return new HashMap<>();
}
@Override
public void write(String message) {
}
@Override
public void write(byte[] message) {
}
@Override
public void close() {
}
public void emitConnect(Object data) {
final Packet connectionPacket = new Packet<>();
connectionPacket.type = Parser.CONNECT;
connectionPacket.nsp = "/";
connectionPacket.data = data;
(new IOParser.Encoder()).encode(connectionPacket, encodedConnectionPacket -> {
final io.socket.engineio.server.parser.Packet dataPacket = new io.socket.engineio.server.parser.Packet<>(io.socket.engineio.server.parser.Packet.MESSAGE);
dataPacket.data = (String) encodedConnectionPacket[0];
io.socket.engineio.server.parser.Parser.PROTOCOL_V4.encodePacket(dataPacket, true, encodedDataPacket -> {
emit("message", encodedDataPacket);
});
});
}
}
================================================
FILE: socket.io-server-test/pom.xml
================================================
socket.io-server-bom
io.socket
4.1.2
4.0.0
socket.io-server-test
jar
This module contains integration tests.
true
true
org.junit.platform
junit-platform-launcher
1.6.3
test
org.junit.jupiter
junit-jupiter-engine
5.6.3
test
org.junit.vintage
junit-vintage-engine
5.6.3
test
org.mockito
mockito-core
5.4.0
test
org.eclipse.jetty
jetty-server
${jetty.version}
test
org.eclipse.jetty
jetty-servlet
${jetty.version}
test
org.eclipse.jetty.websocket
websocket-jetty-server
${jetty.version}
test
io.socket
engine.io-server
6.3.2
test
io.socket
socket.io-server
${project.version}
org.apache.maven.plugins
maven-compiler-plugin
3.11.0
1.8
1.8
UTF-8
-Xlint:unchecked
true
true
org.apache.maven.plugins
maven-deploy-plugin
3.1.1
true
org.codehaus.mojo
exec-maven-plugin
3.1.0
npm-install
process-test-resources
exec
./src/test/resources
npm
install
org.apache.maven.plugins
maven-surefire-plugin
3.1.2
-Dfile.encoding=UTF-8
java.util.logging.config.file
./src/test/resources/logging.properties
================================================
FILE: socket.io-server-test/pom.xml.versionsBackup
================================================
socket.io-server-bom
io.socket
4.1.1
4.0.0
socket.io-server-test
jar
This module contains integration tests.
true
true
org.junit.platform
junit-platform-launcher
1.6.3
test
org.junit.jupiter
junit-jupiter-engine
5.6.3
test
org.junit.vintage
junit-vintage-engine
5.6.3
test
org.mockito
mockito-core
5.4.0
test
org.eclipse.jetty
jetty-server
${jetty.version}
test
org.eclipse.jetty
jetty-servlet
${jetty.version}
test
org.eclipse.jetty.websocket
websocket-jetty-server
${jetty.version}
test
io.socket
engine.io-server
6.3.2
test
io.socket
socket.io-server
${project.version}
org.apache.maven.plugins
maven-compiler-plugin
3.11.0
1.8
1.8
UTF-8
-Xlint:unchecked
true
true
org.apache.maven.plugins
maven-deploy-plugin
3.1.1
true
org.codehaus.mojo
exec-maven-plugin
3.1.0
npm-install
process-test-resources
exec
./src/test/resources
npm
install
org.apache.maven.plugins
maven-surefire-plugin
3.1.2
-Dfile.encoding=UTF-8
java.util.logging.config.file
./src/test/resources/logging.properties
================================================
FILE: socket.io-server-test/src/test/java/io/socket/socketio/server/JettyEngineIoWebSocketHandler.java
================================================
package io.socket.socketio.server;
import io.socket.engineio.server.EngineIoServer;
import io.socket.engineio.server.EngineIoWebSocket;
import io.socket.engineio.server.utils.ParseQS;
import org.eclipse.jetty.websocket.api.Session;
import org.eclipse.jetty.websocket.api.WebSocketListener;
import org.eclipse.jetty.websocket.api.annotations.WebSocket;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.List;
import java.util.Map;
@WebSocket
final class JettyEngineIoWebSocketHandler extends EngineIoWebSocket implements WebSocketListener {
private final EngineIoServer mEngineIoServer;
private Session mSession;
private Map mQuery;
private Map> mHeaders;
public JettyEngineIoWebSocketHandler(EngineIoServer engineIoServer) {
mEngineIoServer = engineIoServer;
}
@Override
public void onWebSocketConnect(Session session) {
mSession = session;
mQuery = ParseQS.decode(session.getUpgradeRequest().getQueryString());
mHeaders = session.getUpgradeRequest().getHeaders();
mEngineIoServer.handleWebSocket(this);
}
@Override
public void onWebSocketClose(int statusCode, String reason) {
emit("close");
mSession = null;
}
@Override
public void onWebSocketError(Throwable cause) {
emit("error", "write error", cause.getMessage());
}
@Override
public void onWebSocketText(String message) {
//noinspection RedundantCast
emit("message", (Object) message);
}
@Override
public void onWebSocketBinary(byte[] payload, int offset, int len) {
byte[] message = payload;
if (!(offset == 0 && len == payload.length)) {
message = new byte[len];
System.arraycopy(payload, offset, message, 0, len);
}
//noinspection RedundantCast
emit("message", (Object) message);
}
@Override
public Map getQuery() {
return mQuery;
}
@Override
public Map> getConnectionHeaders() {
return mHeaders;
}
@Override
public void write(String message) throws IOException {
assert mSession != null;
mSession.getRemote().sendString(message);
}
@Override
public void write(byte[] message) throws IOException {
assert mSession != null;
mSession.getRemote().sendBytes(ByteBuffer.wrap(message));
}
@Override
public void close() {
if (mSession != null) {
mSession.close();
}
}
}
================================================
FILE: socket.io-server-test/src/test/java/io/socket/socketio/server/ServerWrapper.java
================================================
package io.socket.socketio.server;
import io.socket.engineio.server.EngineIoServer;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.HandlerList;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.websocket.server.JettyWebSocketServlet;
import org.eclipse.jetty.websocket.server.JettyWebSocketServletFactory;
import org.eclipse.jetty.websocket.server.config.JettyWebSocketServletContainerInitializer;
import java.io.IOException;
import java.util.concurrent.atomic.AtomicInteger;
final class ServerWrapper {
private static final AtomicInteger PORT_START = new AtomicInteger(3000);
private final int mPort;
private final Server mServer;
private final EngineIoServer mEngineIoServer;
private final SocketIoServer mSocketIoServer;
static {
Log.setLog(new JettyNoLogging());
}
ServerWrapper() {
mPort = PORT_START.getAndIncrement();
mServer = new Server(mPort);
mEngineIoServer = new EngineIoServer();
mSocketIoServer = new SocketIoServer(mEngineIoServer);
System.setProperty("org.eclipse.jetty.util.log.class", "org.eclipse.jetty.util.log.StdErrLog");
System.setProperty("org.eclipse.jetty.LEVEL", "OFF");
ServletContextHandler servletContextHandler = new ServletContextHandler(ServletContextHandler.SESSIONS);
servletContextHandler.setContextPath("/");
final JettyWebSocketServlet webSocketServlet = new JettyWebSocketServlet() {
private static final long serialVersionUID = 4525525859144703715L;
@Override
protected void configure(JettyWebSocketServletFactory jettyWebSocketServletFactory) {
jettyWebSocketServletFactory.addMapping(
"/",
(request, response) -> new JettyEngineIoWebSocketHandler(mEngineIoServer));
}
@Override
public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException {
if (request instanceof HttpServletRequest) {
final String upgradeHeader = ((HttpServletRequest) request).getHeader("upgrade");
if (upgradeHeader != null) {
super.service(request, response);
} else {
mEngineIoServer.handleRequest((HttpServletRequest) request, (HttpServletResponse) response);
}
} else {
super.service(request, response);
}
}
};
final ServletHolder webSocketServletHolder = new ServletHolder(webSocketServlet);
webSocketServletHolder.setAsyncSupported(false);
servletContextHandler.addServlet(webSocketServletHolder, "/socket.io/*");
JettyWebSocketServletContainerInitializer.configure(servletContextHandler, null);
HandlerList handlerList = new HandlerList();
handlerList.setHandlers(new Handler[] { servletContextHandler });
mServer.setHandler(handlerList);
}
void startServer() throws Exception {
mServer.start();
}
void stopServer() throws Exception {
mServer.stop();
}
int getPort() {
return mPort;
}
SocketIoServer getSocketIoServer() {
return mSocketIoServer;
}
private static final class JettyNoLogging implements Logger {
@Override
public String getName() {
return "no";
}
@Override
public void warn(String s, Object... objects) {
}
@Override
public void warn(Throwable throwable) {
}
@Override
public void warn(String s, Throwable throwable) {
}
@Override
public void info(String s, Object... objects) {
}
@Override
public void info(Throwable throwable) {
}
@Override
public void info(String s, Throwable throwable) {
}
@Override
public boolean isDebugEnabled() {
return false;
}
@Override
public void setDebugEnabled(boolean b) {
}
@Override
public void debug(String s, Object... objects) {
}
@Override
public void debug(String s, long l) {
}
@Override
public void debug(Throwable throwable) {
}
@Override
public void debug(String s, Throwable throwable) {
}
@Override
public Logger getLogger(String s) {
return this;
}
@Override
public void ignore(Throwable throwable) {
}
}
}
================================================
FILE: socket.io-server-test/src/test/java/io/socket/socketio/server/SocketIoTest.java
================================================
package io.socket.socketio.server;
import io.socket.engineio.server.Emitter;
import org.junit.Test;
import org.mockito.Mockito;
import java.util.ArrayList;
import java.util.regex.Pattern;
import static org.junit.Assert.*;
public final class SocketIoTest {
@Test
public void test_connect() throws Exception {
final ServerWrapper serverWrapper = new ServerWrapper();
try {
serverWrapper.startServer();
assertEquals(0, Utils.executeScriptForResult("src/test/resources/test_connect.js", serverWrapper.getPort()));
} finally {
serverWrapper.stopServer();
}
}
@Test
public void test_connect_dynamic() throws Exception {
final ServerWrapper serverWrapper = new ServerWrapper();
serverWrapper.getSocketIoServer().namespace(Pattern.compile("^/foo[0-9]$"));
try {
serverWrapper.startServer();
assertEquals(0, Utils.executeScriptForResult("src/test/resources/test_connect_dynamic.js", serverWrapper.getPort()));
} finally {
serverWrapper.stopServer();
}
}
@Test
public void test_message_to_server_nonbinary_noack() throws Exception {
final ServerWrapper serverWrapper = new ServerWrapper();
final Emitter.Listener messageListener = Mockito.mock(Emitter.Listener.class);
Mockito.doAnswer(invocationOnMock -> {
final Object[] messageArgs = invocationOnMock.getArguments();
assertEquals(2, messageArgs.length);
assertEquals(1, messageArgs[0]);
assertEquals("bar", messageArgs[1]);
return null;
}).when(messageListener).call(Mockito.any());
serverWrapper.getSocketIoServer().namespace("/").on("connection", args -> {
final SocketIoSocket socket = (SocketIoSocket) args[0];
socket.on("foo", messageListener);
});
try {
serverWrapper.startServer();
assertEquals(0, Utils.executeScriptForResult("src/test/resources/test_message_to_server_nonbinary_noack.js", serverWrapper.getPort()));
Mockito.verify(messageListener, Mockito.times(1)).call(Mockito.any(Object[].class));
} finally {
serverWrapper.stopServer();
}
}
@Test
public void test_message_to_client_nonbinary_noack() throws Exception {
final ServerWrapper serverWrapper = new ServerWrapper();
serverWrapper.getSocketIoServer().namespace("/").on("connection", args -> {
final SocketIoSocket socket = (SocketIoSocket) args[0];
socket.send("foo", "bar");
});
try {
serverWrapper.startServer();
assertEquals(0, Utils.executeScriptForResult("src/test/resources/test_message_to_server_nonbinary_noack.js", serverWrapper.getPort()));
} finally {
serverWrapper.stopServer();
}
}
@Test
public void test_message_to_server_nonbinary_ack() throws Exception {
final ServerWrapper serverWrapper = new ServerWrapper();
final Emitter.Listener messageListener = Mockito.mock(Emitter.Listener.class);
Mockito.doAnswer(invocationOnMock -> {
final Object[] messageArgs = invocationOnMock.getArguments();
assertEquals(3, messageArgs.length);
assertEquals(1, messageArgs[0]);
assertEquals("bar", messageArgs[1]);
assertTrue(messageArgs[2] instanceof SocketIoSocket.ReceivedByLocalAcknowledgementCallback);
((SocketIoSocket.ReceivedByLocalAcknowledgementCallback) messageArgs[2]).sendAcknowledgement("baz");
return null;
}).when(messageListener).call(Mockito.any(Object[].class));
serverWrapper.getSocketIoServer().namespace("/").on("connection", args -> {
final SocketIoSocket socket = (SocketIoSocket) args[0];
socket.on("foo", messageListener);
});
try {
serverWrapper.startServer();
assertEquals(0, Utils.executeScriptForResult("src/test/resources/test_message_to_server_nonbinary_ack.js", serverWrapper.getPort()));
Mockito.verify(messageListener, Mockito.times(1)).call(Mockito.any(Object[].class));
} finally {
serverWrapper.stopServer();
}
}
@Test
public void test_message_to_client_nonbinary_ack() throws Exception {
final ServerWrapper serverWrapper = new ServerWrapper();
final SocketIoSocket.ReceivedByRemoteAcknowledgementCallback acknowledgementCallback = Mockito.mock(SocketIoSocket.ReceivedByRemoteAcknowledgementCallback.class);
Mockito.doAnswer(invocationOnMock -> {
final Object[] ackArgs = invocationOnMock.getArguments();
assertEquals(1, ackArgs.length);
assertEquals("baz", ackArgs[0]);
return null;
}).when(acknowledgementCallback).onReceivedByRemote(Mockito.any());
serverWrapper.getSocketIoServer().namespace("/").on("connection", args -> {
final SocketIoSocket socket = (SocketIoSocket) args[0];
socket.send("foo", new Object[]{"bar"}, acknowledgementCallback);
});
try {
serverWrapper.startServer();
assertEquals(0, Utils.executeScriptForResult("src/test/resources/test_message_to_client_nonbinary_ack.js", serverWrapper.getPort()));
Mockito.verify(acknowledgementCallback, Mockito.times(1)).onReceivedByRemote(Mockito.any());
} finally {
serverWrapper.stopServer();
}
}
@Test
public void test_message_to_server_binary_noack() throws Exception {
final ServerWrapper serverWrapper = new ServerWrapper();
final byte[] binaryValue = new byte[] {
0, 1, 2, 3, 4, 5, 6, 7
};
final Emitter.Listener messageListener = Mockito.mock(Emitter.Listener.class);
Mockito.doAnswer(invocationOnMock -> {
final Object[] messageArgs = invocationOnMock.getArguments();
assertEquals(1, messageArgs.length);
assertTrue(messageArgs[0] instanceof byte[]);
assertArrayEquals(binaryValue, (byte[])messageArgs[0]);
return null;
}).when(messageListener).call(Mockito.any());
serverWrapper.getSocketIoServer().namespace("/").on("connection", args -> {
final SocketIoSocket socket = (SocketIoSocket) args[0];
socket.on("foo", messageListener);
});
try {
serverWrapper.startServer();
assertEquals(0, Utils.executeScriptForResult("src/test/resources/test_message_to_server_binary_noack.js", serverWrapper.getPort()));
Mockito.verify(messageListener, Mockito.times(1)).call(Mockito.any());
} finally {
serverWrapper.stopServer();
}
}
@Test
public void test_message_to_client_binary_noack() throws Exception {
final ServerWrapper serverWrapper = new ServerWrapper();
final byte[] binaryValue = new byte[] {
0, 1, 2, 3, 4, 5, 6, 7
};
serverWrapper.getSocketIoServer().namespace("/").on("connection", args -> {
final SocketIoSocket socket = (SocketIoSocket) args[0];
socket.send("foo", (Object)binaryValue);
});
try {
serverWrapper.startServer();
assertEquals(0, Utils.executeScriptForResult("src/test/resources/test_message_to_client_binary_noack.js", serverWrapper.getPort()));
} finally {
serverWrapper.stopServer();
}
}
@Test
public void test_broadcast_to_all_clients() throws Exception {
final ServerWrapper serverWrapper = new ServerWrapper();
final SocketIoNamespace namespace = serverWrapper.getSocketIoServer().namespace("/");
namespace.on(
"connection",
args -> namespace.broadcast(null, "foo"));
try {
serverWrapper.startServer();
assertEquals(0, Utils.executeScriptForResult("src/test/resources/test_broadcast_to_all_clients.js", serverWrapper.getPort()));
} finally {
serverWrapper.stopServer();
}
}
@Test
public void test_broadcast_to_one_room() throws Exception {
final ServerWrapper serverWrapper = new ServerWrapper();
final SocketIoNamespace namespace = serverWrapper.getSocketIoServer().namespace("/");
namespace.on("connection", args -> {
final SocketIoSocket socket = (SocketIoSocket) args[0];
socket.on("join", args1 -> {
socket.joinRoom("foo_room");
namespace.broadcast("foo_room", "foo");
});
});
try {
serverWrapper.startServer();
assertEquals(0, Utils.executeScriptForResult("src/test/resources/test_broadcast_to_one_room.js", serverWrapper.getPort()));
} finally {
serverWrapper.stopServer();
}
}
@Test
public void test_broadcast_to_multiple_rooms() throws Exception {
final ServerWrapper serverWrapper = new ServerWrapper();
final SocketIoNamespace namespace = serverWrapper.getSocketIoServer().namespace("/");
namespace.on("connection", args -> {
final SocketIoSocket socket = (SocketIoSocket) args[0];
socket.on("join_foo", args1 -> {
socket.joinRoom("foo_room");
namespace.broadcast("foo_room", "foo");
});
socket.on("join_bar", args1 -> {
socket.joinRoom("bar_room");
namespace.broadcast("bar_room", "bar");
});
});
try {
serverWrapper.startServer();
assertEquals(0, Utils.executeScriptForResult("src/test/resources/test_broadcast_to_multiple_rooms.js", serverWrapper.getPort()));
} finally {
serverWrapper.stopServer();
}
}
@Test
public void test_broadcast_to_all_clients_except_one() throws Exception {
final ServerWrapper serverWrapper = new ServerWrapper();
final SocketIoNamespace namespace = serverWrapper.getSocketIoServer().namespace("/");
final ArrayList socketList = new ArrayList<>();
namespace.on("connection", args -> {
final SocketIoSocket socket = (SocketIoSocket) args[0];
synchronized (socketList) {
if (socketList.size() <= 0) {
socketList.add(socket);
}
}
socket.on("foo", args1 -> socketList.get(0).broadcast(null, "bar"));
});
try {
serverWrapper.startServer();
assertEquals(0, Utils.executeScriptForResult("src/test/resources/test_broadcast_to_all_clients_except_one.js", serverWrapper.getPort()));
} finally {
serverWrapper.stopServer();
}
}
}
================================================
FILE: socket.io-server-test/src/test/java/io/socket/socketio/server/Utils.java
================================================
package io.socket.socketio.server;
import java.io.IOException;
final class Utils {
private Utils() {
}
static int executeScriptForResult(String script, int port) throws IOException {
Process process = Runtime.getRuntime().exec("node " + script, new String[] {
"PORT=" + port
});
try {
int result = process.waitFor();
/*InputStream inputStream = process.getInputStream();
byte[] buffer = new byte[inputStream.available()];
//noinspection ResultOfMethodCallIgnored
inputStream.read(buffer);
System.out.println("[script:stdout]\n" + (new String(buffer, StandardCharsets.UTF_8)));*/
return result;
} catch (InterruptedException ex) {
throw new RuntimeException(ex);
}
}
}
================================================
FILE: socket.io-server-test/src/test/resources/package.json
================================================
{
"private": true,
"dependencies": {
"get-stdin": "^6.0.0",
"socket.io-client": "4.7.1"
}
}
================================================
FILE: socket.io-server-test/src/test/resources/test_broadcast_to_all_clients.js
================================================
var io = require('socket.io-client');
var port = process.env.PORT || 3000;
var fooReceived = [false, false];
function testReceived() {
if (fooReceived[0] && fooReceived[1]) {
process.exit(0);
}
}
var socket1 = io('http://127.0.0.1:' + port, {
autoConnect: false,
transports: ['websocket']
});
socket1.on('foo', function () {
fooReceived[0] = true;
testReceived();
});
var socket2 = io('http://127.0.0.1:' + port, {
autoConnect: false,
transports: ['websocket']
});
socket2.on('foo', function () {
fooReceived[1] = true;
testReceived();
});
socket1.connect();
socket2.connect();
setTimeout(function () {
process.exit(1);
}, 2000);
================================================
FILE: socket.io-server-test/src/test/resources/test_broadcast_to_all_clients_except_one.js
================================================
var io = require('socket.io-client');
var port = process.env.PORT || 3000;
var barReceived = [false, false];
var socket1 = io('http://127.0.0.1:' + port, {
autoConnect: false,
forceNew: true,
transports: ['websocket']
});
socket1.on('connect', function () {
socket1.emit('foo');
});
socket1.on('bar', function () {
barReceived[0] = true;
});
var socket2 = io('http://127.0.0.1:' + port, {
autoConnect: false,
forceNew: true,
transports: ['websocket']
});
socket2.on('connect', function () {
socket2.emit('foo');
});
socket2.on('bar', function () {
barReceived[1] = true;
});
socket1.connect();
socket2.connect();
setTimeout(function () {
console.log(JSON.stringify(barReceived));
if (barReceived[0] !== barReceived[1]) {
process.exit(0);
} else {
process.exit(1);
}
}, 2000);
================================================
FILE: socket.io-server-test/src/test/resources/test_broadcast_to_multiple_rooms.js
================================================
var io = require('socket.io-client');
var port = process.env.PORT || 3000;
var fooReceived = [false, false, false];
var barReceived = [false, false, false];
var socket1 = io('http://127.0.0.1:' + port, {
autoConnect: false,
transports: ['websocket']
});
socket1.on('connect', function() {
socket1.emit('join_foo');
socket1.emit('join_bar');
});
socket1.on('foo', function () {
fooReceived[0] = true;
});
socket1.on('bar', function () {
barReceived[0] = true;
});
var socket2 = io('http://127.0.0.1:' + port, {
autoConnect: false,
transports: ['websocket']
});
socket2.on('connect', function() {
socket2.emit('join_foo');
});
socket2.on('foo', function () {
fooReceived[1] = true;
});
socket2.on('bar', function () {
barReceived[1] = true;
});
var socket3 = io('http://127.0.0.1:' + port, {
autoConnect: false,
transports: ['websocket']
});
socket3.on('connect', function() {
socket3.emit('join_bar');
});
socket3.on('foo', function () {
fooReceived[2] = true;
});
socket3.on('bar', function () {
barReceived[2] = true;
});
socket1.connect();
socket2.connect();
socket3.connect();
setTimeout(function () {
if (fooReceived[0] && fooReceived[1] && !fooReceived[2] &&
barReceived[0] && !barReceived[1] && barReceived[2]) {
process.exit(0);
} else {
process.exit(1);
}
}, 5000);
================================================
FILE: socket.io-server-test/src/test/resources/test_broadcast_to_one_room.js
================================================
var io = require('socket.io-client');
var port = process.env.PORT || 3000;
var fooReceived = [false, false, false];
var socket1 = io('http://127.0.0.1:' + port, {
autoConnect: false,
transports: ['websocket']
});
socket1.on('connect', function() {
socket1.emit('join');
});
socket1.on('foo', function () {
fooReceived[0] = true;
});
var socket2 = io('http://127.0.0.1:' + port, {
autoConnect: false,
transports: ['websocket']
});
socket2.on('connect', function() {
socket2.emit('join');
});
socket2.on('foo', function () {
fooReceived[1] = true;
});
var socket3 = io('http://127.0.0.1:' + port, {
autoConnect: false,
transports: ['websocket']
});
socket3.on('foo', function () {
fooReceived[2] = true;
});
socket1.connect();
socket2.connect();
socket3.connect();
setTimeout(function () {
if (fooReceived[0] && fooReceived[1] && !fooReceived[2]) {
process.exit(0);
} else {
process.exit(1);
}
}, 2000);
================================================
FILE: socket.io-server-test/src/test/resources/test_connect.js
================================================
var io = require('socket.io-client');
var port = process.env.PORT || 3000;
var socket = io('http://127.0.0.1:' + port, {
autoConnect: false,
transports: ['websocket']
});
socket.on('connect', function () {
process.exit(0);
});
socket.connect();
setTimeout(function () {
process.exit(1);
}, 2000);
================================================
FILE: socket.io-server-test/src/test/resources/test_connect_dynamic.js
================================================
var io = require('socket.io-client');
var port = process.env.PORT || 3000;
var fooConnections = {
0: false,
'a': false,
1: false
};
var barConnection = false;
var socket1 = io('http://127.0.0.1:' + port + '/foo0', {
autoConnect: false,
transports: ['websocket']
});
socket1.on('connect', function () {
fooConnections[0] = true;
});
socket1.connect();
var socket2 = io('http://127.0.0.1:' + port + '/fooa', {
autoConnect: false,
transports: ['websocket']
});
socket2.on('connect', function () {
fooConnections['a'] = true;
});
socket2.connect();
var socket3 = io('http://127.0.0.1:' + port + '/foo1', {
autoConnect: false,
transports: ['websocket']
});
socket3.on('connect', function () {
fooConnections[1] = true;
});
socket3.connect();
var socket4 = io('http://127.0.0.1:' + port + '/bar', {
autoConnect: false,
transports: ['websocket']
});
socket4.on('connect', function () {
barConnection = true;
});
socket4.connect();
setTimeout(function () {
if (fooConnections[0] && fooConnections[1] && !fooConnections['a'] && !barConnection) {
process.exit(0);
} else {
process.exit(1);
}
}, 2000);
================================================
FILE: socket.io-server-test/src/test/resources/test_message_to_client_binary_noack.js
================================================
var io = require('socket.io-client');
var port = process.env.PORT || 3000;
var binaryData = new ArrayBuffer(8);
var view = new Uint8Array(binaryData);
view[0] = 0;
view[1] = 1;
view[2] = 2;
view[3] = 3;
view[4] = 4;
view[5] = 5;
view[6] = 6;
view[7] = 7;
var socket = io('http://127.0.0.1:' + port, {
autoConnect: false,
transports: ['websocket']
});
socket.on('foo', function (data) {
if (data && data.length === 8) {
process.exit(0);
}
});
socket.connect();
setTimeout(function () {
process.exit(1);
}, 2000);
================================================
FILE: socket.io-server-test/src/test/resources/test_message_to_client_nonbinary_ack.js
================================================
var io = require('socket.io-client');
var port = process.env.PORT || 3000;
var socket = io('http://127.0.0.1:' + port, {
autoConnect: false,
transports: ['websocket']
});
socket.on('foo', function (bar, callback) {
if (bar === 'bar') {
callback('baz');
process.exit(0);
}
});
socket.connect();
setTimeout(function () {
process.exit(1);
}, 2000);
================================================
FILE: socket.io-server-test/src/test/resources/test_message_to_client_nonbinary_noack.js
================================================
var io = require('socket.io-client');
var port = process.env.PORT || 3000;
var socket = io('http://127.0.0.1:' + port, {
autoConnect: false,
transports: ['websocket']
});
socket.on('foo', function (bar) {
if (bar === 'bar') {
process.exit(0);
}
});
socket.connect();
setTimeout(function () {
process.exit(1);
}, 2000);
================================================
FILE: socket.io-server-test/src/test/resources/test_message_to_server_binary_noack.js
================================================
var io = require('socket.io-client');
var port = process.env.PORT || 3000;
var binaryData = new ArrayBuffer(8);
var view = new Uint8Array(binaryData);
view[0] = 0;
view[1] = 1;
view[2] = 2;
view[3] = 3;
view[4] = 4;
view[5] = 5;
view[6] = 6;
view[7] = 7;
var socket = io('http://127.0.0.1:' + port, {
autoConnect: false,
transports: ['websocket']
});
socket.on('connect', function () {
socket.emit('foo', binaryData);
// This is necessary because engine.io defers some writes
setTimeout(function () {
process.exit(0);
}, 100);
});
socket.connect();
setTimeout(function () {
process.exit(1);
}, 2000);
================================================
FILE: socket.io-server-test/src/test/resources/test_message_to_server_nonbinary_ack.js
================================================
var io = require('socket.io-client');
var port = process.env.PORT || 3000;
var socket = io('http://127.0.0.1:' + port, {
autoConnect: false,
transports: ['websocket']
});
socket.on('connect', function () {
socket.emit('foo', 1, 'bar', function(baz) {
// Ack received
if (baz === 'baz') {
process.exit(0);
}
});
});
socket.connect();
setTimeout(function () {
process.exit(1);
}, 2000);
================================================
FILE: socket.io-server-test/src/test/resources/test_message_to_server_nonbinary_noack.js
================================================
var io = require('socket.io-client');
var port = process.env.PORT || 3000;
var socket = io('http://127.0.0.1:' + port, {
autoConnect: false,
transports: ['websocket']
});
socket.on('connect', function () {
socket.emit('foo', 1, 'bar');
setTimeout(function () {
process.exit(0);
}, 100);
});
socket.connect();
setTimeout(function () {
process.exit(1);
}, 2000);