This commit is contained in:
djc
2022-11-29 04:27:31 +00:00
parent 6799ddb435
commit da5b8dc642
13 changed files with 182 additions and 195 deletions
+13 -14
View File
@@ -6,7 +6,6 @@
<title>Page not found - Quinn</title>
<base href="/">
<!-- Custom HTML head -->
<meta content="text/html; charset=utf-8" http-equiv="Content-Type">
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="theme-color" content="#ffffff" />
@@ -29,13 +28,13 @@
</head>
<body>
<!-- Provide site root to javascript -->
<script type="text/javascript">
<script>
var path_to_root = "";
var default_theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "navy" : "light";
</script>
<!-- Work around some values being stored in localStorage wrapped in quotes -->
<script type="text/javascript">
<script>
try {
var theme = localStorage.getItem('mdbook-theme');
var sidebar = localStorage.getItem('mdbook-sidebar');
@@ -51,7 +50,7 @@
</script>
<!-- Set the theme before any content is loaded, prevents flash -->
<script type="text/javascript">
<script>
var theme;
try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
if (theme === null || theme === undefined) { theme = default_theme; }
@@ -63,7 +62,7 @@
</script>
<!-- Hide / unhide sidebar before it is displayed -->
<script type="text/javascript">
<script>
var html = document.querySelector('html');
var sidebar = 'hidden';
if (document.body.clientWidth >= 1080) {
@@ -94,7 +93,7 @@
<i class="fa fa-paint-brush"></i>
</button>
<ul id="theme-list" class="theme-popup" aria-label="Themes" role="menu">
<li role="none"><button role="menuitem" class="theme" id="light">Light (default)</button></li>
<li role="none"><button role="menuitem" class="theme" id="light">Light</button></li>
<li role="none"><button role="menuitem" class="theme" id="rust">Rust</button></li>
<li role="none"><button role="menuitem" class="theme" id="coal">Coal</button></li>
<li role="none"><button role="menuitem" class="theme" id="navy">Navy</button></li>
@@ -125,7 +124,7 @@
</div>
</div>
<!-- Apply ARIA attributes after the sidebar and the sidebar toggle button are added to the DOM -->
<script type="text/javascript">
<script>
document.getElementById('sidebar-toggle').setAttribute('aria-expanded', sidebar === 'visible');
document.getElementById('sidebar').setAttribute('aria-hidden', sidebar !== 'visible');
Array.from(document.querySelectorAll('#sidebar a')).forEach(function(link) {
@@ -152,15 +151,15 @@
</div>
<script type="text/javascript">
<script>
window.playground_copyable = true;
</script>
<script src="elasticlunr.min.js" type="text/javascript" charset="utf-8"></script>
<script src="mark.min.js" type="text/javascript" charset="utf-8"></script>
<script src="searcher.js" type="text/javascript" charset="utf-8"></script>
<script src="clipboard.min.js" type="text/javascript" charset="utf-8"></script>
<script src="highlight.js" type="text/javascript" charset="utf-8"></script>
<script src="book.js" type="text/javascript" charset="utf-8"></script>
<script src="elasticlunr.min.js"></script>
<script src="mark.min.js"></script>
<script src="searcher.js"></script>
<script src="clipboard.min.js"></script>
<script src="highlight.js"></script>
<script src="book.js"></script>
<!-- Custom JS scripts -->
</body>
+9 -1
View File
@@ -11,7 +11,7 @@ function playground_text(playground) {
let editor = window.ace.edit(code_block);
return editor.getValue();
} else {
return code_block.textContent;
return code_block.innerText;
}
}
@@ -300,6 +300,13 @@ function playground_text(playground) {
themePopup.querySelector("button#" + get_theme()).focus();
}
function updateThemeSelected() {
themePopup.querySelectorAll('.theme-selected').forEach(function (el) {
el.classList.remove('theme-selected');
});
themePopup.querySelector("button#" + get_theme()).classList.add('theme-selected');
}
function hideThemes() {
themePopup.style.display = 'none';
themeToggleButton.setAttribute('aria-expanded', false);
@@ -355,6 +362,7 @@ function playground_text(playground) {
html.classList.remove(previousTheme);
html.classList.add(theme);
updateThemeSelected();
}
// Set theme
+9 -5
View File
@@ -507,6 +507,8 @@ ul#searchresults span.teaser em {
padding: 0;
list-style: none;
display: none;
/* Don't let the children's background extend past the rounded corners. */
overflow: hidden;
}
.theme-popup .default {
color: var(--icons);
@@ -515,7 +517,7 @@ ul#searchresults span.teaser em {
width: 100%;
border: 0;
margin: 0;
padding: 2px 10px;
padding: 2px 20px;
line-height: 25px;
white-space: nowrap;
text-align: left;
@@ -527,8 +529,10 @@ ul#searchresults span.teaser em {
.theme-popup .theme:hover {
background-color: var(--theme-hover);
}
.theme-popup .theme:hover:first-child,
.theme-popup .theme:hover:last-child {
border-top-left-radius: inherit;
border-top-right-radius: inherit;
.theme-selected::before {
display: inline-block;
content: "✓";
margin-left: -14px;
width: 14px;
}
+14 -2
View File
@@ -22,8 +22,8 @@ body {
}
code {
font-family: "Source Code Pro", Consolas, "Ubuntu Mono", Menlo, "DejaVu Sans Mono", monospace, monospace !important;
font-size: 0.875em; /* please adjust the ace font size accordingly in editor.js */
font-family: var(--mono-font) !important;
font-size: var(--code-font-size);
}
/* make long words/inline code not x overflow */
@@ -148,6 +148,18 @@ blockquote {
border-bottom: .1em solid var(--quote-border);
}
kbd {
background-color: var(--table-border-color);
border-radius: 4px;
border: solid 1px var(--theme-popup-border);
box-shadow: inset 0 -1px 0 var(--theme-hover);
display: inline-block;
font-size: var(--code-font-size);
font-family: var(--mono-font);
line-height: 10px;
padding: 4px 5px;
vertical-align: middle;
}
:not(.footnote-definition) + .footnote-definition,
.footnote-definition + :not(.footnote-definition) {
+2
View File
@@ -6,6 +6,8 @@
--page-padding: 15px;
--content-max-width: 750px;
--menu-bar-height: 50px;
--mono-font: "Source Code Pro", Consolas, "Ubuntu Mono", Menlo, "DejaVu Sans Mono", monospace, monospace;
--code-font-size: 0.875em /* please adjust the ace font size accordingly in editor.js */
}
/* Themes */
+13 -14
View File
@@ -5,7 +5,6 @@
<meta charset="UTF-8">
<title>Networking introduction - Quinn</title>
<!-- Custom HTML head -->
<meta content="text/html; charset=utf-8" http-equiv="Content-Type">
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="theme-color" content="#ffffff" />
@@ -28,13 +27,13 @@
</head>
<body>
<!-- Provide site root to javascript -->
<script type="text/javascript">
<script>
var path_to_root = "";
var default_theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "navy" : "light";
</script>
<!-- Work around some values being stored in localStorage wrapped in quotes -->
<script type="text/javascript">
<script>
try {
var theme = localStorage.getItem('mdbook-theme');
var sidebar = localStorage.getItem('mdbook-sidebar');
@@ -50,7 +49,7 @@
</script>
<!-- Set the theme before any content is loaded, prevents flash -->
<script type="text/javascript">
<script>
var theme;
try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
if (theme === null || theme === undefined) { theme = default_theme; }
@@ -62,7 +61,7 @@
</script>
<!-- Hide / unhide sidebar before it is displayed -->
<script type="text/javascript">
<script>
var html = document.querySelector('html');
var sidebar = 'hidden';
if (document.body.clientWidth >= 1080) {
@@ -93,7 +92,7 @@
<i class="fa fa-paint-brush"></i>
</button>
<ul id="theme-list" class="theme-popup" aria-label="Themes" role="menu">
<li role="none"><button role="menuitem" class="theme" id="light">Light (default)</button></li>
<li role="none"><button role="menuitem" class="theme" id="light">Light</button></li>
<li role="none"><button role="menuitem" class="theme" id="rust">Rust</button></li>
<li role="none"><button role="menuitem" class="theme" id="coal">Coal</button></li>
<li role="none"><button role="menuitem" class="theme" id="navy">Navy</button></li>
@@ -124,7 +123,7 @@
</div>
</div>
<!-- Apply ARIA attributes after the sidebar and the sidebar toggle button are added to the DOM -->
<script type="text/javascript">
<script>
document.getElementById('sidebar-toggle').setAttribute('aria-expanded', sidebar === 'visible');
document.getElementById('sidebar').setAttribute('aria-hidden', sidebar !== 'visible');
Array.from(document.querySelectorAll('#sidebar a')).forEach(function(link) {
@@ -193,15 +192,15 @@ However, data loss of a single packet will still block all response streams beca
</div>
<script type="text/javascript">
<script>
window.playground_copyable = true;
</script>
<script src="elasticlunr.min.js" type="text/javascript" charset="utf-8"></script>
<script src="mark.min.js" type="text/javascript" charset="utf-8"></script>
<script src="searcher.js" type="text/javascript" charset="utf-8"></script>
<script src="clipboard.min.js" type="text/javascript" charset="utf-8"></script>
<script src="highlight.js" type="text/javascript" charset="utf-8"></script>
<script src="book.js" type="text/javascript" charset="utf-8"></script>
<script src="elasticlunr.min.js"></script>
<script src="mark.min.js"></script>
<script src="searcher.js"></script>
<script src="clipboard.min.js"></script>
<script src="highlight.js"></script>
<script src="book.js"></script>
<!-- Custom JS scripts -->
</body>
+13 -14
View File
@@ -5,7 +5,6 @@
<meta charset="UTF-8">
<title>Networking introduction - Quinn</title>
<!-- Custom HTML head -->
<meta content="text/html; charset=utf-8" http-equiv="Content-Type">
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="theme-color" content="#ffffff" />
@@ -28,13 +27,13 @@
</head>
<body>
<!-- Provide site root to javascript -->
<script type="text/javascript">
<script>
var path_to_root = "";
var default_theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "navy" : "light";
</script>
<!-- Work around some values being stored in localStorage wrapped in quotes -->
<script type="text/javascript">
<script>
try {
var theme = localStorage.getItem('mdbook-theme');
var sidebar = localStorage.getItem('mdbook-sidebar');
@@ -50,7 +49,7 @@
</script>
<!-- Set the theme before any content is loaded, prevents flash -->
<script type="text/javascript">
<script>
var theme;
try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
if (theme === null || theme === undefined) { theme = default_theme; }
@@ -62,7 +61,7 @@
</script>
<!-- Hide / unhide sidebar before it is displayed -->
<script type="text/javascript">
<script>
var html = document.querySelector('html');
var sidebar = 'hidden';
if (document.body.clientWidth >= 1080) {
@@ -93,7 +92,7 @@
<i class="fa fa-paint-brush"></i>
</button>
<ul id="theme-list" class="theme-popup" aria-label="Themes" role="menu">
<li role="none"><button role="menuitem" class="theme" id="light">Light (default)</button></li>
<li role="none"><button role="menuitem" class="theme" id="light">Light</button></li>
<li role="none"><button role="menuitem" class="theme" id="rust">Rust</button></li>
<li role="none"><button role="menuitem" class="theme" id="coal">Coal</button></li>
<li role="none"><button role="menuitem" class="theme" id="navy">Navy</button></li>
@@ -124,7 +123,7 @@
</div>
</div>
<!-- Apply ARIA attributes after the sidebar and the sidebar toggle button are added to the DOM -->
<script type="text/javascript">
<script>
document.getElementById('sidebar-toggle').setAttribute('aria-expanded', sidebar === 'visible');
document.getElementById('sidebar').setAttribute('aria-hidden', sidebar !== 'visible');
Array.from(document.querySelectorAll('#sidebar a')).forEach(function(link) {
@@ -193,15 +192,15 @@ However, data loss of a single packet will still block all response streams beca
</div>
<script type="text/javascript">
<script>
window.playground_copyable = true;
</script>
<script src="elasticlunr.min.js" type="text/javascript" charset="utf-8"></script>
<script src="mark.min.js" type="text/javascript" charset="utf-8"></script>
<script src="searcher.js" type="text/javascript" charset="utf-8"></script>
<script src="clipboard.min.js" type="text/javascript" charset="utf-8"></script>
<script src="highlight.js" type="text/javascript" charset="utf-8"></script>
<script src="book.js" type="text/javascript" charset="utf-8"></script>
<script src="elasticlunr.min.js"></script>
<script src="mark.min.js"></script>
<script src="searcher.js"></script>
<script src="clipboard.min.js"></script>
<script src="highlight.js"></script>
<script src="book.js"></script>
<!-- Custom JS scripts -->
</body>
+29 -45
View File
@@ -6,7 +6,6 @@
<title>Quinn</title>
<meta name="robots" content="noindex" />
<!-- Custom HTML head -->
<meta content="text/html; charset=utf-8" http-equiv="Content-Type">
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="theme-color" content="#ffffff" />
@@ -29,13 +28,13 @@
</head>
<body>
<!-- Provide site root to javascript -->
<script type="text/javascript">
<script>
var path_to_root = "";
var default_theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "navy" : "light";
</script>
<!-- Work around some values being stored in localStorage wrapped in quotes -->
<script type="text/javascript">
<script>
try {
var theme = localStorage.getItem('mdbook-theme');
var sidebar = localStorage.getItem('mdbook-sidebar');
@@ -51,7 +50,7 @@
</script>
<!-- Set the theme before any content is loaded, prevents flash -->
<script type="text/javascript">
<script>
var theme;
try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
if (theme === null || theme === undefined) { theme = default_theme; }
@@ -63,7 +62,7 @@
</script>
<!-- Hide / unhide sidebar before it is displayed -->
<script type="text/javascript">
<script>
var html = document.querySelector('html');
var sidebar = 'hidden';
if (document.body.clientWidth >= 1080) {
@@ -94,7 +93,7 @@
<i class="fa fa-paint-brush"></i>
</button>
<ul id="theme-list" class="theme-popup" aria-label="Themes" role="menu">
<li role="none"><button role="menuitem" class="theme" id="light">Light (default)</button></li>
<li role="none"><button role="menuitem" class="theme" id="light">Light</button></li>
<li role="none"><button role="menuitem" class="theme" id="rust">Rust</button></li>
<li role="none"><button role="menuitem" class="theme" id="coal">Coal</button></li>
<li role="none"><button role="menuitem" class="theme" id="navy">Navy</button></li>
@@ -125,7 +124,7 @@
</div>
</div>
<!-- Apply ARIA attributes after the sidebar and the sidebar toggle button are added to the DOM -->
<script type="text/javascript">
<script>
document.getElementById('sidebar-toggle').setAttribute('aria-expanded', sidebar === 'visible');
document.getElementById('sidebar').setAttribute('aria-hidden', sidebar !== 'visible');
Array.from(document.querySelectorAll('#sidebar a')).forEach(function(link) {
@@ -323,8 +322,7 @@ impl rustls::client::ServerCertVerifier for SkipServerVerification {
Ok(rustls::client::ServerCertVerified::assertion())
}
}
<span class="boring">}
</span></code></pre></pre>
<span class="boring">}</span></code></pre></pre>
<p>After that, modify the <a href="https://docs.rs/quinn/latest/quinn/struct.ClientConfig.html">ClientConfig</a> to use this <a href="https://docs.rs/rustls/latest/rustls/client/trait.ServerCertVerifier.html">ServerCertVerifier</a> implementation.</p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
@@ -336,8 +334,7 @@ impl rustls::client::ServerCertVerifier for SkipServerVerification {
ClientConfig::new(Arc::new(crypto))
}
<span class="boring">}
</span></code></pre></pre>
<span class="boring">}</span></code></pre></pre>
<p>Finally, if you plug this <a href="https://docs.rs/quinn/latest/quinn/struct.ClientConfig.html">ClientConfig</a> into the <a href="https://docs.rs/quinn/latest/quinn/struct.Endpoint.html#method.set_default_client_config">Endpoint::set_default_client_config()</a> your client endpoint should verify all connections as trustworthy.</p>
<h2 id="using-certificates"><a class="header" href="#using-certificates">Using Certificates</a></h2>
<p>In this section, we look at certifying an endpoint with a certificate.
@@ -356,8 +353,7 @@ This example uses <a href="https://github.com/est31/rcgen">rcgen</a> to generate
let key = rustls::PrivateKey(cert.serialize_private_key_der());
Ok((rustls::Certificate(cert.serialize_der()?), key))
}
<span class="boring">}
</span></code></pre></pre>
<span class="boring">}</span></code></pre></pre>
<p><em>Note that <a href="https://docs.rs/rcgen/latest/rcgen/fn.generate_simple_self_signed.html">generate_simple_self_signed</a> returns a <a href="https://en.wikipedia.org/wiki/Public_key_certificate">Certificate</a> that can be serialized to both <code>.der</code> and <code>.pem</code> formats.</em></p>
<h3 id="non-self-signed-certificates"><a class="header" href="#non-self-signed-certificates">Non-self-signed Certificates</a></h3>
<p>For this example, we use <a href="https://letsencrypt.org/getting-started/">Let's Encrypt</a>, a well-known Certificate Authority (<a href="https://en.wikipedia.org/wiki/Certificate_authority">CA</a>) (certificate issuer) which distributes certificates for free.</p>
@@ -393,8 +389,7 @@ pub fn read_certs_from_file(
Ok((certs, key))
}
<span class="boring">}
</span></code></pre></pre>
<span class="boring">}</span></code></pre></pre>
<h3 id="configuring-certificates"><a class="header" href="#configuring-certificates">Configuring Certificates</a></h3>
<p>Now that you have a valid certificate, the client and server need to be configured to use it.
After configuring plug the configuration into the <code>Endpoint</code>.</p>
@@ -402,15 +397,13 @@ After configuring plug the configuration into the <code>Endpoint</code>.</p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>let server_config = ServerConfig::with_single_cert(certs, key)?;
<span class="boring">}
</span></code></pre></pre>
<span class="boring">}</span></code></pre></pre>
<p>This is the only thing you need to do for your server to be secured.</p>
<p><strong>Configure Client</strong></p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>let client_config = ClientConfig::with_native_roots();
<span class="boring">}
</span></code></pre></pre>
<span class="boring">}</span></code></pre></pre>
<p>This is the only thing you need to do for your client to trust a server certificate signed by a conventional certificate authority.</p>
<p><br><hr></p>
<p><a href="quinn/set-up-connection.html">Next</a>, let's have a look at how to set up a connection.</p>
@@ -433,8 +426,7 @@ fn client_addr() -&gt; SocketAddr {
fn server_addr() -&gt; SocketAddr {
&quot;127.0.0.1:5001&quot;.parse::&lt;SocketAddr&gt;().unwrap()
}
<span class="boring">}
</span></code></pre></pre>
<span class="boring">}</span></code></pre></pre>
<p><strong>Server</strong></p>
<p>First, the server endpoint should be bound to a socket.
The <a href="https://docs.rs/quinn/latest/quinn/struct.Endpoint.html#method.server">server()</a> method, which can be used for this, returns the <code>Endpoint</code> type.
@@ -454,8 +446,7 @@ The <a href="https://docs.rs/quinn/latest/quinn/struct.Endpoint.html#method.serv
Ok(())
}
<span class="boring">}
</span></code></pre></pre>
<span class="boring">}</span></code></pre></pre>
<p><strong>Client</strong></p>
<p>The <a href="https://docs.rs/quinn/latest/quinn/struct.Endpoint.html#method.client">client()</a> returns only a <code>Endpoint</code> type.
The client needs to connect to the server using the <a href="https://docs.rs/quinn/latest/quinn/struct.Endpoint.html#method.connect">connect(server_name)</a> method.<br />
@@ -473,8 +464,7 @@ The <code>SERVER_NAME</code> argument is the DNS name, matching the certificate
Ok(())
}
<span class="boring">}
</span></code></pre></pre>
<span class="boring">}</span></code></pre></pre>
<p><br><hr></p>
<p><a href="quinn/data-transfer.html">Next up</a>, let's have a look at sending data over this connection.</p>
<div style="break-before: page; page-break-before: always;"></div><h1 id="data-transfer"><a class="header" href="#data-transfer">Data Transfer</a></h1>
@@ -515,8 +505,7 @@ For example, from the connection initiator to the peer and the other way around.
Ok(())
}
<span class="boring">}
</span></code></pre></pre>
<span class="boring">}</span></code></pre></pre>
<p><em>iterate incoming bidirectional stream(s)</em></p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
@@ -531,8 +520,7 @@ For example, from the connection initiator to the peer and the other way around.
Ok(())
}
<span class="boring">}
</span></code></pre></pre>
<span class="boring">}</span></code></pre></pre>
<h2 id="unidirectional-streams"><a class="header" href="#unidirectional-streams">Unidirectional Streams</a></h2>
<p>With unidirectional streams, you can carry data only in one direction: from the initiator of the stream to its peer.
It is possible to get reliability without ordering (so no head-of-line blocking) by opening a new stream for each packet.</p>
@@ -549,8 +537,7 @@ It is possible to get reliability without ordering (so no head-of-line blocking)
Ok(())
}
<span class="boring">}
</span></code></pre></pre>
<span class="boring">}</span></code></pre></pre>
<p><em>iterating incoming unidirectional stream(s)</em></p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
@@ -562,8 +549,7 @@ It is possible to get reliability without ordering (so no head-of-line blocking)
Ok(())
}
<span class="boring">}
</span></code></pre></pre>
<span class="boring">}</span></code></pre></pre>
<h2 id="unreliable-messaging"><a class="header" href="#unreliable-messaging">Unreliable Messaging</a></h2>
<p>With unreliable messaging, you can transfer data without reliability.
This could be useful if data arrival isn't essential or when high throughput is important.</p>
@@ -577,8 +563,7 @@ This could be useful if data arrival isn't essential or when high throughput is
Ok(())
}
<span class="boring">}
</span></code></pre></pre>
<span class="boring">}</span></code></pre></pre>
<p><em>iterating datagram stream(s)</em></p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
@@ -590,8 +575,7 @@ This could be useful if data arrival isn't essential or when high throughput is
Ok(())
}
<span class="boring">}
</span></code></pre></pre>
<span class="boring">}</span></code></pre></pre>
</main>
@@ -607,18 +591,18 @@ This could be useful if data arrival isn't essential or when high throughput is
</div>
<script type="text/javascript">
<script>
window.playground_copyable = true;
</script>
<script src="elasticlunr.min.js" type="text/javascript" charset="utf-8"></script>
<script src="mark.min.js" type="text/javascript" charset="utf-8"></script>
<script src="searcher.js" type="text/javascript" charset="utf-8"></script>
<script src="clipboard.min.js" type="text/javascript" charset="utf-8"></script>
<script src="highlight.js" type="text/javascript" charset="utf-8"></script>
<script src="book.js" type="text/javascript" charset="utf-8"></script>
<script src="elasticlunr.min.js"></script>
<script src="mark.min.js"></script>
<script src="searcher.js"></script>
<script src="clipboard.min.js"></script>
<script src="highlight.js"></script>
<script src="book.js"></script>
<!-- Custom JS scripts -->
<script type="text/javascript">
<script>
window.addEventListener('load', function() {
window.setTimeout(window.print, 100);
});
+13 -14
View File
@@ -5,7 +5,6 @@
<meta charset="UTF-8">
<title>QUIC introduction - Quinn</title>
<!-- Custom HTML head -->
<meta content="text/html; charset=utf-8" http-equiv="Content-Type">
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="theme-color" content="#ffffff" />
@@ -28,13 +27,13 @@
</head>
<body>
<!-- Provide site root to javascript -->
<script type="text/javascript">
<script>
var path_to_root = "";
var default_theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "navy" : "light";
</script>
<!-- Work around some values being stored in localStorage wrapped in quotes -->
<script type="text/javascript">
<script>
try {
var theme = localStorage.getItem('mdbook-theme');
var sidebar = localStorage.getItem('mdbook-sidebar');
@@ -50,7 +49,7 @@
</script>
<!-- Set the theme before any content is loaded, prevents flash -->
<script type="text/javascript">
<script>
var theme;
try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
if (theme === null || theme === undefined) { theme = default_theme; }
@@ -62,7 +61,7 @@
</script>
<!-- Hide / unhide sidebar before it is displayed -->
<script type="text/javascript">
<script>
var html = document.querySelector('html');
var sidebar = 'hidden';
if (document.body.clientWidth >= 1080) {
@@ -93,7 +92,7 @@
<i class="fa fa-paint-brush"></i>
</button>
<ul id="theme-list" class="theme-popup" aria-label="Themes" role="menu">
<li role="none"><button role="menuitem" class="theme" id="light">Light (default)</button></li>
<li role="none"><button role="menuitem" class="theme" id="light">Light</button></li>
<li role="none"><button role="menuitem" class="theme" id="rust">Rust</button></li>
<li role="none"><button role="menuitem" class="theme" id="coal">Coal</button></li>
<li role="none"><button role="menuitem" class="theme" id="navy">Navy</button></li>
@@ -124,7 +123,7 @@
</div>
</div>
<!-- Apply ARIA attributes after the sidebar and the sidebar toggle button are added to the DOM -->
<script type="text/javascript">
<script>
document.getElementById('sidebar-toggle').setAttribute('aria-expanded', sidebar === 'visible');
document.getElementById('sidebar').setAttribute('aria-hidden', sidebar !== 'visible');
Array.from(document.querySelectorAll('#sidebar a')).forEach(function(link) {
@@ -181,15 +180,15 @@ One thing is for sure, QUIC has many great potentials and will serve us in the f
</div>
<script type="text/javascript">
<script>
window.playground_copyable = true;
</script>
<script src="elasticlunr.min.js" type="text/javascript" charset="utf-8"></script>
<script src="mark.min.js" type="text/javascript" charset="utf-8"></script>
<script src="searcher.js" type="text/javascript" charset="utf-8"></script>
<script src="clipboard.min.js" type="text/javascript" charset="utf-8"></script>
<script src="highlight.js" type="text/javascript" charset="utf-8"></script>
<script src="book.js" type="text/javascript" charset="utf-8"></script>
<script src="elasticlunr.min.js"></script>
<script src="mark.min.js"></script>
<script src="searcher.js"></script>
<script src="clipboard.min.js"></script>
<script src="highlight.js"></script>
<script src="book.js"></script>
<!-- Custom JS scripts -->
</body>
+13 -14
View File
@@ -5,7 +5,6 @@
<meta charset="UTF-8">
<title>QUINN Introduction - Quinn</title>
<!-- Custom HTML head -->
<meta content="text/html; charset=utf-8" http-equiv="Content-Type">
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="theme-color" content="#ffffff" />
@@ -28,13 +27,13 @@
</head>
<body>
<!-- Provide site root to javascript -->
<script type="text/javascript">
<script>
var path_to_root = "";
var default_theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "navy" : "light";
</script>
<!-- Work around some values being stored in localStorage wrapped in quotes -->
<script type="text/javascript">
<script>
try {
var theme = localStorage.getItem('mdbook-theme');
var sidebar = localStorage.getItem('mdbook-sidebar');
@@ -50,7 +49,7 @@
</script>
<!-- Set the theme before any content is loaded, prevents flash -->
<script type="text/javascript">
<script>
var theme;
try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
if (theme === null || theme === undefined) { theme = default_theme; }
@@ -62,7 +61,7 @@
</script>
<!-- Hide / unhide sidebar before it is displayed -->
<script type="text/javascript">
<script>
var html = document.querySelector('html');
var sidebar = 'hidden';
if (document.body.clientWidth >= 1080) {
@@ -93,7 +92,7 @@
<i class="fa fa-paint-brush"></i>
</button>
<ul id="theme-list" class="theme-popup" aria-label="Themes" role="menu">
<li role="none"><button role="menuitem" class="theme" id="light">Light (default)</button></li>
<li role="none"><button role="menuitem" class="theme" id="light">Light</button></li>
<li role="none"><button role="menuitem" class="theme" id="rust">Rust</button></li>
<li role="none"><button role="menuitem" class="theme" id="coal">Coal</button></li>
<li role="none"><button role="menuitem" class="theme" id="navy">Navy</button></li>
@@ -124,7 +123,7 @@
</div>
</div>
<!-- Apply ARIA attributes after the sidebar and the sidebar toggle button are added to the DOM -->
<script type="text/javascript">
<script>
document.getElementById('sidebar-toggle').setAttribute('aria-expanded', sidebar === 'visible');
document.getElementById('sidebar').setAttribute('aria-hidden', sidebar !== 'visible');
Array.from(document.querySelectorAll('#sidebar a')).forEach(function(link) {
@@ -256,15 +255,15 @@ crates will always be at least 6 months old at the time of release.</p>
</div>
<script type="text/javascript">
<script>
window.playground_copyable = true;
</script>
<script src="elasticlunr.min.js" type="text/javascript" charset="utf-8"></script>
<script src="mark.min.js" type="text/javascript" charset="utf-8"></script>
<script src="searcher.js" type="text/javascript" charset="utf-8"></script>
<script src="clipboard.min.js" type="text/javascript" charset="utf-8"></script>
<script src="highlight.js" type="text/javascript" charset="utf-8"></script>
<script src="book.js" type="text/javascript" charset="utf-8"></script>
<script src="elasticlunr.min.js"></script>
<script src="mark.min.js"></script>
<script src="searcher.js"></script>
<script src="clipboard.min.js"></script>
<script src="highlight.js"></script>
<script src="book.js"></script>
<!-- Custom JS scripts -->
</body>
+19 -26
View File
@@ -5,7 +5,6 @@
<meta charset="UTF-8">
<title>Certificate Configuration - Quinn</title>
<!-- Custom HTML head -->
<meta content="text/html; charset=utf-8" http-equiv="Content-Type">
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="theme-color" content="#ffffff" />
@@ -28,13 +27,13 @@
</head>
<body>
<!-- Provide site root to javascript -->
<script type="text/javascript">
<script>
var path_to_root = "../";
var default_theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "navy" : "light";
</script>
<!-- Work around some values being stored in localStorage wrapped in quotes -->
<script type="text/javascript">
<script>
try {
var theme = localStorage.getItem('mdbook-theme');
var sidebar = localStorage.getItem('mdbook-sidebar');
@@ -50,7 +49,7 @@
</script>
<!-- Set the theme before any content is loaded, prevents flash -->
<script type="text/javascript">
<script>
var theme;
try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
if (theme === null || theme === undefined) { theme = default_theme; }
@@ -62,7 +61,7 @@
</script>
<!-- Hide / unhide sidebar before it is displayed -->
<script type="text/javascript">
<script>
var html = document.querySelector('html');
var sidebar = 'hidden';
if (document.body.clientWidth >= 1080) {
@@ -93,7 +92,7 @@
<i class="fa fa-paint-brush"></i>
</button>
<ul id="theme-list" class="theme-popup" aria-label="Themes" role="menu">
<li role="none"><button role="menuitem" class="theme" id="light">Light (default)</button></li>
<li role="none"><button role="menuitem" class="theme" id="light">Light</button></li>
<li role="none"><button role="menuitem" class="theme" id="rust">Rust</button></li>
<li role="none"><button role="menuitem" class="theme" id="coal">Coal</button></li>
<li role="none"><button role="menuitem" class="theme" id="navy">Navy</button></li>
@@ -124,7 +123,7 @@
</div>
</div>
<!-- Apply ARIA attributes after the sidebar and the sidebar toggle button are added to the DOM -->
<script type="text/javascript">
<script>
document.getElementById('sidebar-toggle').setAttribute('aria-expanded', sidebar === 'visible');
document.getElementById('sidebar').setAttribute('aria-hidden', sidebar !== 'visible');
Array.from(document.querySelectorAll('#sidebar a')).forEach(function(link) {
@@ -169,8 +168,7 @@ impl rustls::client::ServerCertVerifier for SkipServerVerification {
Ok(rustls::client::ServerCertVerified::assertion())
}
}
<span class="boring">}
</span></code></pre></pre>
<span class="boring">}</span></code></pre></pre>
<p>After that, modify the <a href="https://docs.rs/quinn/latest/quinn/struct.ClientConfig.html">ClientConfig</a> to use this <a href="https://docs.rs/rustls/latest/rustls/client/trait.ServerCertVerifier.html">ServerCertVerifier</a> implementation.</p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
@@ -182,8 +180,7 @@ impl rustls::client::ServerCertVerifier for SkipServerVerification {
ClientConfig::new(Arc::new(crypto))
}
<span class="boring">}
</span></code></pre></pre>
<span class="boring">}</span></code></pre></pre>
<p>Finally, if you plug this <a href="https://docs.rs/quinn/latest/quinn/struct.ClientConfig.html">ClientConfig</a> into the <a href="https://docs.rs/quinn/latest/quinn/struct.Endpoint.html#method.set_default_client_config">Endpoint::set_default_client_config()</a> your client endpoint should verify all connections as trustworthy.</p>
<h2 id="using-certificates"><a class="header" href="#using-certificates">Using Certificates</a></h2>
<p>In this section, we look at certifying an endpoint with a certificate.
@@ -202,8 +199,7 @@ This example uses <a href="https://github.com/est31/rcgen">rcgen</a> to generate
let key = rustls::PrivateKey(cert.serialize_private_key_der());
Ok((rustls::Certificate(cert.serialize_der()?), key))
}
<span class="boring">}
</span></code></pre></pre>
<span class="boring">}</span></code></pre></pre>
<p><em>Note that <a href="https://docs.rs/rcgen/latest/rcgen/fn.generate_simple_self_signed.html">generate_simple_self_signed</a> returns a <a href="https://en.wikipedia.org/wiki/Public_key_certificate">Certificate</a> that can be serialized to both <code>.der</code> and <code>.pem</code> formats.</em></p>
<h3 id="non-self-signed-certificates"><a class="header" href="#non-self-signed-certificates">Non-self-signed Certificates</a></h3>
<p>For this example, we use <a href="https://letsencrypt.org/getting-started/">Let's Encrypt</a>, a well-known Certificate Authority (<a href="https://en.wikipedia.org/wiki/Certificate_authority">CA</a>) (certificate issuer) which distributes certificates for free.</p>
@@ -239,8 +235,7 @@ pub fn read_certs_from_file(
Ok((certs, key))
}
<span class="boring">}
</span></code></pre></pre>
<span class="boring">}</span></code></pre></pre>
<h3 id="configuring-certificates"><a class="header" href="#configuring-certificates">Configuring Certificates</a></h3>
<p>Now that you have a valid certificate, the client and server need to be configured to use it.
After configuring plug the configuration into the <code>Endpoint</code>.</p>
@@ -248,15 +243,13 @@ After configuring plug the configuration into the <code>Endpoint</code>.</p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>let server_config = ServerConfig::with_single_cert(certs, key)?;
<span class="boring">}
</span></code></pre></pre>
<span class="boring">}</span></code></pre></pre>
<p>This is the only thing you need to do for your server to be secured.</p>
<p><strong>Configure Client</strong></p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>let client_config = ClientConfig::with_native_roots();
<span class="boring">}
</span></code></pre></pre>
<span class="boring">}</span></code></pre></pre>
<p>This is the only thing you need to do for your client to trust a server certificate signed by a conventional certificate authority.</p>
<p><br><hr></p>
<p><a href="set-up-connection.html">Next</a>, let's have a look at how to set up a connection.</p>
@@ -287,15 +280,15 @@ After configuring plug the configuration into the <code>Endpoint</code>.</p>
</div>
<script type="text/javascript">
<script>
window.playground_copyable = true;
</script>
<script src="../elasticlunr.min.js" type="text/javascript" charset="utf-8"></script>
<script src="../mark.min.js" type="text/javascript" charset="utf-8"></script>
<script src="../searcher.js" type="text/javascript" charset="utf-8"></script>
<script src="../clipboard.min.js" type="text/javascript" charset="utf-8"></script>
<script src="../highlight.js" type="text/javascript" charset="utf-8"></script>
<script src="../book.js" type="text/javascript" charset="utf-8"></script>
<script src="../elasticlunr.min.js"></script>
<script src="../mark.min.js"></script>
<script src="../searcher.js"></script>
<script src="../clipboard.min.js"></script>
<script src="../highlight.js"></script>
<script src="../book.js"></script>
<!-- Custom JS scripts -->
</body>
+19 -26
View File
@@ -5,7 +5,6 @@
<meta charset="UTF-8">
<title>Data Transfer - Quinn</title>
<!-- Custom HTML head -->
<meta content="text/html; charset=utf-8" http-equiv="Content-Type">
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="theme-color" content="#ffffff" />
@@ -28,13 +27,13 @@
</head>
<body>
<!-- Provide site root to javascript -->
<script type="text/javascript">
<script>
var path_to_root = "../";
var default_theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "navy" : "light";
</script>
<!-- Work around some values being stored in localStorage wrapped in quotes -->
<script type="text/javascript">
<script>
try {
var theme = localStorage.getItem('mdbook-theme');
var sidebar = localStorage.getItem('mdbook-sidebar');
@@ -50,7 +49,7 @@
</script>
<!-- Set the theme before any content is loaded, prevents flash -->
<script type="text/javascript">
<script>
var theme;
try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
if (theme === null || theme === undefined) { theme = default_theme; }
@@ -62,7 +61,7 @@
</script>
<!-- Hide / unhide sidebar before it is displayed -->
<script type="text/javascript">
<script>
var html = document.querySelector('html');
var sidebar = 'hidden';
if (document.body.clientWidth >= 1080) {
@@ -93,7 +92,7 @@
<i class="fa fa-paint-brush"></i>
</button>
<ul id="theme-list" class="theme-popup" aria-label="Themes" role="menu">
<li role="none"><button role="menuitem" class="theme" id="light">Light (default)</button></li>
<li role="none"><button role="menuitem" class="theme" id="light">Light</button></li>
<li role="none"><button role="menuitem" class="theme" id="rust">Rust</button></li>
<li role="none"><button role="menuitem" class="theme" id="coal">Coal</button></li>
<li role="none"><button role="menuitem" class="theme" id="navy">Navy</button></li>
@@ -124,7 +123,7 @@
</div>
</div>
<!-- Apply ARIA attributes after the sidebar and the sidebar toggle button are added to the DOM -->
<script type="text/javascript">
<script>
document.getElementById('sidebar-toggle').setAttribute('aria-expanded', sidebar === 'visible');
document.getElementById('sidebar').setAttribute('aria-hidden', sidebar !== 'visible');
Array.from(document.querySelectorAll('#sidebar a')).forEach(function(link) {
@@ -172,8 +171,7 @@ For example, from the connection initiator to the peer and the other way around.
Ok(())
}
<span class="boring">}
</span></code></pre></pre>
<span class="boring">}</span></code></pre></pre>
<p><em>iterate incoming bidirectional stream(s)</em></p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
@@ -188,8 +186,7 @@ For example, from the connection initiator to the peer and the other way around.
Ok(())
}
<span class="boring">}
</span></code></pre></pre>
<span class="boring">}</span></code></pre></pre>
<h2 id="unidirectional-streams"><a class="header" href="#unidirectional-streams">Unidirectional Streams</a></h2>
<p>With unidirectional streams, you can carry data only in one direction: from the initiator of the stream to its peer.
It is possible to get reliability without ordering (so no head-of-line blocking) by opening a new stream for each packet.</p>
@@ -206,8 +203,7 @@ It is possible to get reliability without ordering (so no head-of-line blocking)
Ok(())
}
<span class="boring">}
</span></code></pre></pre>
<span class="boring">}</span></code></pre></pre>
<p><em>iterating incoming unidirectional stream(s)</em></p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
@@ -219,8 +215,7 @@ It is possible to get reliability without ordering (so no head-of-line blocking)
Ok(())
}
<span class="boring">}
</span></code></pre></pre>
<span class="boring">}</span></code></pre></pre>
<h2 id="unreliable-messaging"><a class="header" href="#unreliable-messaging">Unreliable Messaging</a></h2>
<p>With unreliable messaging, you can transfer data without reliability.
This could be useful if data arrival isn't essential or when high throughput is important.</p>
@@ -234,8 +229,7 @@ This could be useful if data arrival isn't essential or when high throughput is
Ok(())
}
<span class="boring">}
</span></code></pre></pre>
<span class="boring">}</span></code></pre></pre>
<p><em>iterating datagram stream(s)</em></p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
@@ -247,8 +241,7 @@ This could be useful if data arrival isn't essential or when high throughput is
Ok(())
}
<span class="boring">}
</span></code></pre></pre>
<span class="boring">}</span></code></pre></pre>
</main>
@@ -270,15 +263,15 @@ This could be useful if data arrival isn't essential or when high throughput is
</div>
<script type="text/javascript">
<script>
window.playground_copyable = true;
</script>
<script src="../elasticlunr.min.js" type="text/javascript" charset="utf-8"></script>
<script src="../mark.min.js" type="text/javascript" charset="utf-8"></script>
<script src="../searcher.js" type="text/javascript" charset="utf-8"></script>
<script src="../clipboard.min.js" type="text/javascript" charset="utf-8"></script>
<script src="../highlight.js" type="text/javascript" charset="utf-8"></script>
<script src="../book.js" type="text/javascript" charset="utf-8"></script>
<script src="../elasticlunr.min.js"></script>
<script src="../mark.min.js"></script>
<script src="../searcher.js"></script>
<script src="../clipboard.min.js"></script>
<script src="../highlight.js"></script>
<script src="../book.js"></script>
<!-- Custom JS scripts -->
</body>
+16 -20
View File
@@ -5,7 +5,6 @@
<meta charset="UTF-8">
<title>Connection Setup - Quinn</title>
<!-- Custom HTML head -->
<meta content="text/html; charset=utf-8" http-equiv="Content-Type">
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="theme-color" content="#ffffff" />
@@ -28,13 +27,13 @@
</head>
<body>
<!-- Provide site root to javascript -->
<script type="text/javascript">
<script>
var path_to_root = "../";
var default_theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "navy" : "light";
</script>
<!-- Work around some values being stored in localStorage wrapped in quotes -->
<script type="text/javascript">
<script>
try {
var theme = localStorage.getItem('mdbook-theme');
var sidebar = localStorage.getItem('mdbook-sidebar');
@@ -50,7 +49,7 @@
</script>
<!-- Set the theme before any content is loaded, prevents flash -->
<script type="text/javascript">
<script>
var theme;
try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
if (theme === null || theme === undefined) { theme = default_theme; }
@@ -62,7 +61,7 @@
</script>
<!-- Hide / unhide sidebar before it is displayed -->
<script type="text/javascript">
<script>
var html = document.querySelector('html');
var sidebar = 'hidden';
if (document.body.clientWidth >= 1080) {
@@ -93,7 +92,7 @@
<i class="fa fa-paint-brush"></i>
</button>
<ul id="theme-list" class="theme-popup" aria-label="Themes" role="menu">
<li role="none"><button role="menuitem" class="theme" id="light">Light (default)</button></li>
<li role="none"><button role="menuitem" class="theme" id="light">Light</button></li>
<li role="none"><button role="menuitem" class="theme" id="rust">Rust</button></li>
<li role="none"><button role="menuitem" class="theme" id="coal">Coal</button></li>
<li role="none"><button role="menuitem" class="theme" id="navy">Navy</button></li>
@@ -124,7 +123,7 @@
</div>
</div>
<!-- Apply ARIA attributes after the sidebar and the sidebar toggle button are added to the DOM -->
<script type="text/javascript">
<script>
document.getElementById('sidebar-toggle').setAttribute('aria-expanded', sidebar === 'visible');
document.getElementById('sidebar').setAttribute('aria-hidden', sidebar !== 'visible');
Array.from(document.querySelectorAll('#sidebar a')).forEach(function(link) {
@@ -153,8 +152,7 @@ fn client_addr() -&gt; SocketAddr {
fn server_addr() -&gt; SocketAddr {
&quot;127.0.0.1:5001&quot;.parse::&lt;SocketAddr&gt;().unwrap()
}
<span class="boring">}
</span></code></pre></pre>
<span class="boring">}</span></code></pre></pre>
<p><strong>Server</strong></p>
<p>First, the server endpoint should be bound to a socket.
The <a href="https://docs.rs/quinn/latest/quinn/struct.Endpoint.html#method.server">server()</a> method, which can be used for this, returns the <code>Endpoint</code> type.
@@ -174,8 +172,7 @@ The <a href="https://docs.rs/quinn/latest/quinn/struct.Endpoint.html#method.serv
Ok(())
}
<span class="boring">}
</span></code></pre></pre>
<span class="boring">}</span></code></pre></pre>
<p><strong>Client</strong></p>
<p>The <a href="https://docs.rs/quinn/latest/quinn/struct.Endpoint.html#method.client">client()</a> returns only a <code>Endpoint</code> type.
The client needs to connect to the server using the <a href="https://docs.rs/quinn/latest/quinn/struct.Endpoint.html#method.connect">connect(server_name)</a> method.<br />
@@ -193,8 +190,7 @@ The <code>SERVER_NAME</code> argument is the DNS name, matching the certificate
Ok(())
}
<span class="boring">}
</span></code></pre></pre>
<span class="boring">}</span></code></pre></pre>
<p><br><hr></p>
<p><a href="data-transfer.html">Next up</a>, let's have a look at sending data over this connection.</p>
@@ -224,15 +220,15 @@ The <code>SERVER_NAME</code> argument is the DNS name, matching the certificate
</div>
<script type="text/javascript">
<script>
window.playground_copyable = true;
</script>
<script src="../elasticlunr.min.js" type="text/javascript" charset="utf-8"></script>
<script src="../mark.min.js" type="text/javascript" charset="utf-8"></script>
<script src="../searcher.js" type="text/javascript" charset="utf-8"></script>
<script src="../clipboard.min.js" type="text/javascript" charset="utf-8"></script>
<script src="../highlight.js" type="text/javascript" charset="utf-8"></script>
<script src="../book.js" type="text/javascript" charset="utf-8"></script>
<script src="../elasticlunr.min.js"></script>
<script src="../mark.min.js"></script>
<script src="../searcher.js"></script>
<script src="../clipboard.min.js"></script>
<script src="../highlight.js"></script>
<script src="../book.js"></script>
<!-- Custom JS scripts -->
</body>