Files
nexus/sreweekly/articles/449/04-accelerating-connection-handshakes.html
2026-09-12 17:23:01 +08:00

3718 lines
197 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="Follow an overview of methods like TCP FastOpen, TLSv1.3, 0-RTT, and HTTP/3 to reduce handshake delays and improve server response times in secure environments.">
<meta name="keywords" content="Transmission Control Protocol, TLS, connection, data, network, requests">
<meta property="og:description" content="Follow an overview of methods like TCP FastOpen, TLSv1.3, 0-RTT, and HTTP/3 to reduce handshake delays and improve server response times in secure environments.">
<meta property="og:site_name" content="dzone.com">
<meta property="og:title" content="Accelerating Connection Handshakes">
<meta property="og:url" content="https://dzone.com/articles/accelerating-connection-handshakes">
<meta property="og:image" content="https://dz2cdn1.dzone.com/storage/article-thumb/18002355-thumb.jpg">
<meta property="og:type" content="article">
<meta name="twitter:site" content="@DZoneInc">
<meta name="twitter:image" content="https://dz2cdn1.dzone.com/storage/article-thumb/18002355-thumb.jpg">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:description" content="Follow an overview of methods like TCP FastOpen, TLSv1.3, 0-RTT, and HTTP/3 to reduce handshake delays and improve server response times in secure environments.">
<meta name="twitter:title" content="Accelerating Connection Handshakes">
<meta name="referrer" content="origin-when-cross-origin">
<meta name="google-site-verification" content="kndbhxcupfEqWmZclhCpB6vlgOs7QSmx2UHAGGnP2mA">
<meta name="df-verify" content="df0d76632b4543">
<link rel="icon" type="image/x-icon" href="https://dz2cdn1.dzone.com/themes/dz20/images/favicon.png">
<link rel="image_src" href="https://dz2cdn1.dzone.com/storage/article-thumb/18002355-thumb.jpg">
<link rel="canonical" href="https://dzone.com/articles/accelerating-connection-handshakes">
<title>Accelerating Connection Handshakes</title>
<link rel="preload" href="https://dz2cdn1.dzone.com/themes/dz20/font/fontello.woff?11773374" as="font" type="font/woff" crossorigin="anonymous">
<link rel="stylesheet" media="all" href="https://dz2cdn1.dzone.com/themes/dz20/ftl/icons.css">
<link rel="stylesheet" media="all" href="https://dz2cdn1.dzone.com/themes/dz20/lib/static/bootstrap/bootstrap.min.css">
<link rel="stylesheet" media="all" href="https://dz2cdn1.dzone.com/themes/dz20/ftl/article/global.css">
<link rel="stylesheet" media="all" href="https://dz2cdn1.dzone.com/themes/dz20/ftl/header-updated/styles.css">
<style>
:root {
--xs-size: 2px;
--sm-size: 5px;
--md-size: 10px;
--lg-size: 15px;
--xl-size: 25px;
--sm-border-size: 8px;
--sm-font-size: 13px;
--sm-plus-font-size: 16px;
--md-font-size: 18px;
--lg-font-size: 22px;
--xl-font-size: 26px;
}
/* Display Quick-Access Classes */
.display-none { display: none; }
.display-block { display: block; }
.display-inline { display: inline; }
.display-inline-block { display: inline-block; }
/* Margin Quick-Access Classes */
.m-xs { margin: var(--xs-size); }
.m-sm { margin: var(--sm-size); }
.m-md { margin: var(--md-size); }
.m-lg { margin: var(--lg-size); }
.m-xl { margin: var(--xl-size); }
.mt-xs, .my-xs { margin-top: var(--xs-size); }
.mr-xs, .mx-xs { margin-right: var(--xs-size); }
.mb-xs, .my-xs { margin-bottom: var(--xs-size); }
.ml-xs, .mx-xs { margin-left: var(--xs-size); }
.mt-sm, .my-sm { margin-top: var(--sm-size); }
.mr-sm, .mx-sm { margin-right: var(--sm-size); }
.mb-sm, .my-sm { margin-bottom: var(--sm-size); }
.ml-sm, .mx-sm { margin-left: var(--sm-size); }
.mt-md, .my-md { margin-top: var(--md-size); }
.mr-md, .mx-md { margin-right: var(--md-size); }
.mb-md, .my-md { margin-bottom: var(--md-size); }
.ml-md, .mx-md { margin-left: var(--md-size); }
.mt-lg, .my-lg { margin-top: var(--lg-size); }
.mr-lg, .mx-lg { margin-right: var(--lg-size); }
.mb-lg, .my-lg { margin-bottom: var(--lg-size); }
.ml-lg, .mx-lg { margin-left: var(--lg-size); }
.mt-xl, .my-xl { margin-top: var(--xl-size); }
.mr-xl, .mx-xl { margin-right: var(--xl-size); }
.mb-xl, .my-xl { margin-bottom: var(--xl-size); }
.ml-xl, .mx-xl { margin-left: var(--xl-size); }
.ml-auto, .mx-auto { margin-left: auto; }
.mr-auto, .mx-auto { margin-right: auto; }
.mt-auto, .my-auto { margin-top: auto; }
.mb-auto, .my-auto { margin-bottom: auto; }
.my-none, .mt-none { margin-top: 0 !important; }
.mx-none, .mr-none { margin-right: 0 !important; }
.my-none, .mb-none { margin-bottom: 0 !important; }
.mx-none, .ml-none { margin-left: 0 !important; }
/* Padding Quick-Access Classes */
.p-xs { padding: var(--xs-size); }
.p-sm { padding: var(--sm-size); }
.p-md { padding: var(--md-size); }
.p-lg { padding: var(--lg-size); }
.p-xl { padding: var(--xl-size); }
.pt-xs, .py-xs { padding-top: var(--xs-size); }
.pr-xs, .px-xs { padding-right: var(--xs-size); }
.pb-xs, .py-xs { padding-bottom: var(--xs-size); }
.pl-xs, .px-xs { padding-left: var(--xs-size); }
.pt-sm, .py-sm { padding-top: var(--sm-size); }
.pr-sm, .px-sm { padding-right: var(--sm-size); }
.pb-sm, .py-sm { padding-bottom: var(--sm-size); }
.pl-sm, .px-sm { padding-left: var(--sm-size); }
.pt-md, .py-md { padding-top: var(--md-size); }
.pr-md, .px-md { padding-right: var(--md-size); }
.pb-md, .py-md { padding-bottom: var(--md-size); }
.pl-md, .px-md { padding-left: var(--md-size); }
.pt-lg, .py-lg { padding-top: var(--lg-size); }
.pr-lg, .px-lg { padding-right: var(--lg-size); }
.pb-lg, .py-lg { padding-bottom: var(--lg-size); }
.pl-lg, .px-lg { padding-left: var(--lg-size); }
.pt-xl, .py-xl { padding-top: var(--xl-size); }
.pr-xl, .px-xl { padding-right: var(--xl-size); }
.pb-xl, .py-xl { padding-bottom: var(--xl-size); }
.pl-xl, .px-xl { padding-left: var(--xl-size); }
.py-none, .pt-none { padding-top: 0 !important; }
.px-none, .pr-none { padding-right: 0 !important; }
.py-none, .pb-none { padding-bottom: 0 !important; }
.px-none, .pl-none { padding-left: 0 !important; }
/* Flex Quick-Access Classes */
.flex {
display: flex;
}
.flex-column {
display: flex;
flex-direction: column;
}
.flex-column-reverse {
display: flex;
flex-direction: column-reverse;
}
.flex-row {
display: flex;
flex-direction: row;
}
.flex-row-reverse {
display: flex;
flex-direction: row-reverse;
}
.flex-wrap {
flex-wrap: wrap;
}
.flex-grow {
flex-grow: 1;
}
.align-center { align-items: center; }
.align-end { align-items: flex-end; }
.content-center {
align-content: center;
}
.justify-center { justify-content: center; }
.justify-start { justify-content: flex-start; }
.justify-end { justify-content: flex-end; }
.justify-around { justify-content: space-around; }
.justify-between { justify-content: space-between; }
.justify-evenly { justify-content: space-evenly; }
.gap-sm { gap: var(--sm-size); }
.gap-md { gap: var(--md-size); }
.gap-lg { gap: var(--lg-size); }
.gap-xl { gap: var(--xl-size); }
/* Display Quick-Access Classes */
.inline-block { display: inline-block; }
/* Font Quick-Access Classes */
.font-xl { font-size: var(--xl-font-size); }
.font-lg { font-size: var(--lg-font-size); }
.font-md { font-size: var(--md-font-size); }
.font-sm-plus { font-size: var(--sm-plus-font-size); }
.font-sm { font-size: var(--sm-font-size); }
.font-strike { text-decoration: line-through; }
.font-underline { text-decoration: underline; }
.font-overline { text-decoration: overline; }
.font-italic { font-style: italic; }
.font-bold { font-weight: bold; }
.font-black { color: #000000; }
.font-white { color: #ffffff; }
.font-gray { color: var(--brand-gray-text); }
.font-danger { color: var(--header-dropdown-button-bg-danger); }
.font-center { text-align: center; }
.no-wrap { white-space: nowrap; }
/* Consistent headings between h and non-h tags */
.heading {
margin-top: 0;
margin-bottom: var(--md-size);
}
.heading.font-xl { line-height: var(--xl-font-size); }
.heading.font-lg { line-height: var(--lg-font-size); }
.heading.font-md { line-height: var(--md-font-size); }
.heading.font-sm-plus { line-height: var(--sm-plus-font-size); }
.heading.font-sm { line-height: var(--sm-font-size); }
/* Font Clamp Quick-Access Classes */
.line-clamp,
.line-clamp-2,
.line-clamp-3,
.line-clamp-4,
.line-clamp-5 {
display: -webkit-box;
-webkit-line-clamp: 4;
-webkit-box-orient: vertical;
overflow: hidden;
text-overflow: ellipsis;
}
.line-clamp-2 { -webkit-line-clamp: 2 !important; }
.line-clamp-3 { -webkit-line-clamp: 3 !important; }
.line-clamp-4 { -webkit-line-clamp: 4 !important; }
.line-clamp-5 { -webkit-line-clamp: 5 !important; }
/* User-Select Quick-Access Classes */
.select-none { user-select: none; }
/* Position Quick-Access Classes */
.absolute { position: absolute; }
.relative { position: relative; }
.float-left { float: left; }
.float-right { float: right; }
.pos-top { top: 0; }
.pos-right { right: 0; }
.pos-bottom { bottom: 0; }
.pos-left { left: 0; }
.va-top { vertical-align: top; }
.va-middle { vertical-align: middle; }
.va-bottom { vertical-align: bottom; }
/* Transition Quick-Access Classes */
.trans-linear-quick {
transition: all 0.15s linear;
}
/* Background Quick-Access Classes */
.bg-gray { background: #cccccc; }
.bg-white { background-color: white; }
.bg-none { background: none; }
/* Border Quick-Access Classes */
.border-gray { border: 1px solid #cccccc; }
.tab-pane.border-gray { border: 1px solid #ddd; }
.no-border, .border-none { border: none !important; }
.bt-none { border-top: none !important; }
.br-none { border-right: none !important; }
.bb-none { border-bottom: none !important; }
.bl-none { border-left: none !important; }
/* Border Radius Quick-Access Classes */
.round-t-sm, .round-tl-sm { border-top-left-radius: var(--sm-border-size); }
.round-t-sm, .round-tr-sm { border-top-right-radius: var(--sm-border-size); }
.round-b-sm, .round-bl-sm { border-bottom-left-radius: var(--sm-border-size); }
.round-b-sm, .round-br-sm { border-bottom-right-radius: var(--sm-border-size); }
.round-sm { border-radius: var(--sm-border-size); }
.round-fully { border-radius: 50%; }
/* Misc Quick-Access Classes */
.haikei {
position: relative;
background-image: url('https://dz2cdn1.dzone.com/themes/dz20/images/polygon-scatter-haikei-shapes.svg');
background-repeat: no-repeat;
background-size: cover;
}
.haikei:before {
content: '';
position:absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: var(--color-event-background);
z-index: -1;
}
.margin-auto-center {
display: block;
margin-left: auto;
margin-right: auto;
}
.flex-auto-center {
display: flex;
margin-left: auto;
margin-right: auto;
}
.mw-1100 { max-width: 1100px; }
.mw-1300 { max-width: 1300px; }
.full-width { width: 100%; }
.h-separator {
background-color: #cccccc;
height: 1px;
width: 100%;
margin: 5px auto;
}
.no-spacing {
margin: 0 !important;
padding: 0 !important;
}
.inherit-color { color: inherit; }
.inherit-decoration { text-decoration: inherit; }
/* Table Quick-Access Classes */
.table-border-gray tr:not(:last-child) {
border-bottom: 1px solid #cccccc;
}
table.full-width tr td:last-child {
width:100%;
}
/* Animation Quick-Access Classes */
@keyframes anim-spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(359deg); }
}
.anim-spin {
display: inline-block;
animation: anim-spin 2s infinite linear;
}
.hov-pointer { cursor: pointer; }
.hov-brighten:hover { filter: brightness(1.2); }
.hov-darken:hover { filter: brightness(0.95); }
.hov-underline:hover { text-decoration: underline; }
/* Small tweaks related to components when using these styles */
.nav-tabs {
border-bottom: none;
}
.tab-content > .flex-column.active,
.tab-content > .flex-row.active {
display: flex;
}
/* Alpine components with x-cloak should not be visible by default until conditionals kick in */
[x-cloak] { display: none !important; }
</style>
<link rel="stylesheet" media="all" href="https://dz2cdn1.dzone.com/themes/dz20/ftl/alpine-components/modal.css">
<script defer src="https://dz2cdn1.dzone.com/themes/dz20/lib/alpinejs/3.13.2/cdn.min.js"></script>
<script>
document.addEventListener('alpine:init', () => {
Alpine.store('article', {
id: null,
engagement: {
open: false,
page: 1,
users: [],
additional: true,
loading: false,
initializing: false,
unauthorized: false,
enqueued: null,
dequeued: null,
reset() {
this.open = false;
this.page = 1;
this.users.splice(0);
this.additional = true;
this.loading = false;
this.initializing = false;
this.enqueued = null;
this.dequeued = null;
},
enqueue(user) {
this.dequeued = null;
this.enqueued = user;
},
dequeue(user) {
this.enqueued = null;
this.dequeued = user;
},
add(user) {
this.users.push(user);
this.enqueued = null;
},
remove(user) {
const index = this.users.findIndex(u => u.id === user.id);
if (index !== -1) {
this.users.splice(index, 1);
}
this.dequeued = null;
},
contains(user) {
return this.users.filter(u => u.id === user.id).length;
}
}
});
Alpine.effect(() => {
const store = Alpine.store('article');
if (!store.engagement.additional && store.engagement.enqueued) {
if (!store.engagement.contains(store.engagement.enqueued)) {
store.engagement.add(store.engagement.enqueued);
}
}
});
Alpine.effect(() => {
const store = Alpine.store('article');
if (store.engagement.page && store.engagement.dequeued) {
store.engagement.remove(store.engagement.dequeued);
}
})
});
</script></head>
<body x-data>
<div class="skybox skybox-closeBtn dz_skybox" data-gpt-slot="skybox" id="div-gpt-ad-1435246566686-99"></div>
<header id="ftl-header">
<div class="header-top">
<div class="header-container">
<div class="pull-left logo-container">
<div class="logo">
<a class="inner" href="/">
<picture>
<source srcset="https://dz2cdn1.dzone.com/themes/dz20/images/dz_logo_2021_cropped.webp" type="image/webp">
<source srcset="https://dz2cdn1.dzone.com/themes/dz20/images/dz_logo_2021_cropped.png" type="image/png">
<img src="https://dz2cdn1.dzone.com/themes/dz20/images/dz_logo_2021_cropped.png" width="181" height="56" alt="DZone">
</picture>
</a>
</div>
</div>
<div class="pull-right login-and-search">
<div id="authenticated-block" class="logged-in">
<div class="welcome-back">Thanks for visiting DZone today,</div>
<div id="user-header" class="user-info">
<button class="user-avatar">
<span id="header-username" class="username"></span>
<img id="header-avatar" src="" alt="user avatar">
</button>
<div id="user-dropdown" class="browse-user-menu">
<div class="user-content">
<a id="header-user-plug" href="#" class="user-description"></a>
<a id="header-user-edit" href="#" class="edit-profile">Edit Profile</a>
</div>
<ul class="user-actions">
<li id="first-user-action">
<a id="header-dropdown-manage-email" href="#">Manage Email Subscriptions</a>
</li>
<li>
<a href="/articles/how-to-submit-a-post-to-dzone?utm_source=DZone&utm_medium=user_dropdown&utm_campaign=how_to_post">
How to Post to DZone
</a>
</li>
<li>
<a href="/articles/dzones-article-submission-guidelines">
Article Submission Guidelines
</a>
</li>
</ul>
<div class="bottom">
<a href="/users/logout.html" class="sign-out">Sign Out</a>
<a id="dropdown-view-profile" href="#" class="view-profile">View Profile</a>
</div>
</div>
</div>
<div class="post-content">
<button id="post-button" class="post-content--button">
<span class="post-class">Post</span>
<i class="icon-plus"></i>
</button>
<div id="post-menu" class="posting-links">
<div class="posting-links-menu">
<ul>
<li>
<img src="https://dz2cdn1.dzone.com/themes/dz20/images/dz-postarticle.svg" width="15" height="18" style="width: 15px; height: 18px;">
<a href="/content/article/post.html">Post an Article</a>
</li>
<li>
<a id="drafts-link" href="#">Manage My Drafts</a>
</li>
</ul>
</div>
</div>
</div>
</div>
<div id="unauthenticated-block">
<div class="dz-intro">Over 2 million developers have joined DZone.</div>
<div class="mobile-invisible sign-in-join">
<a href="/users/login.html">Log In</a>
<span class="dz-intro-span">/</span>
<a href="/static/registration.html">Join</a>
</div>
<a class="join-icon" href="/users/login.html" aria-label="User">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide svg-user lucide-user-icon lucide-user">
<path d="M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2"></path>
<circle cx="12" cy="7" r="4"></circle>
</svg>
</a>
</div>
<script>
document.addEventListener('alpine:init', () => {
Alpine.data('searchDrawer', () => ({
open: false,
query: '',
minCharsMet: false,
toggleVisibility() {
this.open = !this.open;
if (this.open) {
this.$nextTick(() => {
this.$refs.query.focus();
});
}
},
updateSearchValue() {
if (!this.query || !this.query.length || this.query.length < 3) {
this.minCharsMet = false;
return;
}
this.minCharsMet = true;
localStorage.setItem('ls.searchValue', this.query);
},
searchSite() {
if (this.minCharsMet) {
window.location = '/search';
}
}
}));
});
</script>
<div class="headerSearch" x-data="searchDrawer()" x-cloak>
<button class="btn-search dropdown-toggle" x-on:click="toggleVisibility()" aria-label="Search">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide svg-search lucide-search-icon lucide-search">
<path d="m21 21-4.34-4.34"></path>
<circle cx="11" cy="11" r="8"></circle>
</svg>
</button>
<template x-teleport=".header-container">
<div id="search-drawer" x-show="open" x-on:click.outside="open = false" x-transition>
<div class="search-input">
<input type="text"
placeholder="Search"
autofocus="autofocus"
x-model="query"
x-ref="query"
x-on:input.change="updateSearchValue()"
x-on:keyup.enter="searchSite()"
>
<button x-on:click="searchSite()" x-bind:disabled="!minCharsMet">
<span>Search</span>
</button>
</div>
<div class="search-footer">
Please enter at least three characters to search
</div>
</div>
</template>
</div>
</div>
</div> </div>
<div class="header-bottom">
<div class="header-bottom-container">
<a class="resource-link" href="/refcardz">Refcards</a>
<a class="resource-link" href="/trendreports">Trend Reports</a>
<div class="resource-link link-menu">
<a href="/events">Events</a>
<a href="/events/video-library">Video Library</a>
</div>
</div>
</div>
<nav class="header-menu-bar">
<div class="header-menu resource-category">
<a href="/refcardz">Refcards</a>
</div>
<div class="header-menu-separator resource-category-separator"></div>
<div class="header-menu resource-category">
<a href="/trendreports">Trend Reports</a>
</div>
<div class="header-menu-separator resource-category-separator"></div>
<div class="header-menu resource-category no-bottom-radius" data-click-activation>
<p class="menu-label">Events</p>
<div class="header-menu-items">
<div class="header-menu-columns">
<a class="header-menu-item" href="/events">View Events</a>
<a class="header-menu-item" href="/events/video-library">Video Library</a>
</div>
</div>
</div>
<div class="header-menu zone-menu" data-click-activation>
<p class="menu-label">Zones <i class="icon-down-dir icon-closed"></i><i class="icon-right-dir icon-open"></i></p>
<div class="header-menu-items">
<div class="header-menu-columns">
<div class="header-menu-column-item">
<a class="header-menu-item" href="/culture-and-methodologies">Culture and Methodologies</a>
<a class="header-menu-item" href="/agile">Agile</a>
<a class="header-menu-item" href="/career-development">Career Development</a>
<a class="header-menu-item" href="/methodologies">Methodologies</a>
<a class="header-menu-item" href="/team-management">Team Management</a>
</div>
<div class="header-menu-column-item">
<a class="header-menu-item" href="/data-engineering">Data Engineering</a>
<a class="header-menu-item" href="/ai-ml">AI/ML</a>
<a class="header-menu-item" href="/big-data">Big Data</a>
<a class="header-menu-item" href="/data">Data</a>
<a class="header-menu-item" href="/databases">Databases</a>
<a class="header-menu-item" href="/iot">IoT</a>
</div>
<div class="header-menu-column-item">
<a class="header-menu-item" href="/software-design-and-architecture">Software Design and Architecture</a>
<a class="header-menu-item" href="/cloud-architecture">Cloud Architecture</a>
<a class="header-menu-item" href="/containers">Containers</a>
<a class="header-menu-item" href="/integration">Integration</a>
<a class="header-menu-item" href="/microservices">Microservices</a>
<a class="header-menu-item" href="/performance">Performance</a>
<a class="header-menu-item" href="/security">Security</a>
</div>
<div class="header-menu-column-item">
<a class="header-menu-item" href="/coding">Coding</a>
<a class="header-menu-item" href="/frameworks">Frameworks</a>
<a class="header-menu-item" href="/java">Java</a>
<a class="header-menu-item" href="/javascript">JavaScript</a>
<a class="header-menu-item" href="/languages">Languages</a>
<a class="header-menu-item" href="/tools">Tools</a>
</div>
<div class="header-menu-column-item">
<a class="header-menu-item" href="/testing-deployment-and-maintenance">Testing, Deployment, and Maintenance</a>
<a class="header-menu-item" href="/deployment">Deployment</a>
<a class="header-menu-item" href="/devops-and-cicd">DevOps and CI/CD</a>
<a class="header-menu-item" href="/maintenance">Maintenance</a>
<a class="header-menu-item" href="/monitoring-and-observability">Monitoring and Observability</a>
<a class="header-menu-item" href="/testing-tools-and-frameworks">Testing, Tools, and Frameworks</a>
</div>
<div class="header-menu-column-item sponsored">
<a class="header-menu-item" href="javascript:void(0)">Partner Zones</a>
<a class="header-menu-item" href="/hubs/build-ai-agents-that-are-ready-for-production/">Build AI Agents That Are Ready for Production</a>
</div>
</div>
</div>
</div>
<div class="header-menu parent-category" tabindex="1">
<a href="/culture-and-methodologies">Culture and Methodologies</a>
<div class="header-menu-items">
<a class="header-menu-item" href="/agile">Agile</a>
<a class="header-menu-item" href="/career-development">Career Development</a>
<a class="header-menu-item" href="/methodologies">Methodologies</a>
<a class="header-menu-item" href="/team-management">Team Management</a>
</div>
</div>
<div class="header-menu-separator parent-category-separator"></div>
<div class="header-menu parent-category" tabindex="2">
<a href="/data-engineering">Data Engineering</a>
<div class="header-menu-items">
<a class="header-menu-item" href="/ai-ml">AI/ML</a>
<a class="header-menu-item" href="/big-data">Big Data</a>
<a class="header-menu-item" href="/data">Data</a>
<a class="header-menu-item" href="/databases">Databases</a>
<a class="header-menu-item" href="/iot">IoT</a>
</div>
</div>
<div class="header-menu-separator parent-category-separator"></div>
<div class="header-menu parent-category" tabindex="3">
<a href="/software-design-and-architecture">Software Design and Architecture</a>
<div class="header-menu-items">
<a class="header-menu-item" href="/cloud-architecture">Cloud Architecture</a>
<a class="header-menu-item" href="/containers">Containers</a>
<a class="header-menu-item" href="/integration">Integration</a>
<a class="header-menu-item" href="/microservices">Microservices</a>
<a class="header-menu-item" href="/performance">Performance</a>
<a class="header-menu-item" href="/security">Security</a>
</div>
</div>
<div class="header-menu-separator parent-category-separator"></div>
<div class="header-menu parent-category" tabindex="4">
<a href="/coding">Coding</a>
<div class="header-menu-items">
<a class="header-menu-item" href="/frameworks">Frameworks</a>
<a class="header-menu-item" href="/java">Java</a>
<a class="header-menu-item" href="/javascript">JavaScript</a>
<a class="header-menu-item" href="/languages">Languages</a>
<a class="header-menu-item" href="/tools">Tools</a>
</div>
</div>
<div class="header-menu-separator parent-category-separator"></div>
<div class="header-menu parent-category" tabindex="5">
<a href="/testing-deployment-and-maintenance">Testing, Deployment, and Maintenance</a>
<div class="header-menu-items">
<a class="header-menu-item" href="/deployment">Deployment</a>
<a class="header-menu-item" href="/devops-and-cicd">DevOps and CI/CD</a>
<a class="header-menu-item" href="/maintenance">Maintenance</a>
<a class="header-menu-item" href="/monitoring-and-observability">Monitoring and Observability</a>
<a class="header-menu-item" href="/testing-tools-and-frameworks">Testing, Tools, and Frameworks</a>
</div>
</div>
<div class="header-menu-separator parent-category-separator"></div>
<div class="header-menu parent-category sponsored" tabindex="6">
<a href="javascript:void(0)">Partner Zones</a>
<div class="header-menu-items sponsored">
<a class="header-menu-item" href="/hubs/build-ai-agents-that-are-ready-for-production/">Build AI Agents That Are Ready for Production</a>
</div>
</div>
<div class="header-menu-separator parent-category-separator"></div>
</nav>
</header>
<script>
const csrf = {
parameter: 'TH_CSRF',
header: 'X-TH-CSRF',
token: '-6390741061932621558'
}; // set csrf for auth-status script
</script>
<script>
addEventListener('DOMContentLoaded', function() {
handleRedirects();
handleMenus();
handleMobileMenuHeights();
handleGotoLinks();
});
function isHidden(element) {
try {
return window.getComputedStyle(element).display === 'none';
} catch (_) {
return false;
}
}
function getLink(element) {
if (element.hasAttribute('data-goto')) {
return element.getAttribute('data-goto');
}
return element.href;
}
function isLeftClick(event) {
if (event.altKey || event.shiftKey) {
return false;
} else if ('buttons' in event || 'which' in event) {
return event.buttons === 1 || event.which === 1;
} else {
return (event.button === 1 || (event.type === 'click'));
}
}
function handleRedirects() {
const redirections = [...document.querySelectorAll('[data-activate-menu]'), ...document.querySelectorAll('[data-click-target]')];
redirections.forEach(function(element) {
const menuSelector = element.getAttribute('data-activate-menu') || element.getAttribute('data-click-target');
const redirectingElement = document.querySelector(menuSelector);
if (redirectingElement) {
element.style.cursor = 'pointer';
const redirect = function(e) {
if (redirectingElement.hasAttribute('href') || redirectingElement.hasAttribute('data-goto')) {
if (redirectingElement.hasAttribute('data-new-window') || e.ctrlKey || e.metaKey) {
window.open(getLink(redirectingElement), '_blank');
} else {
window.open(getLink(redirectingElement), '_self');
}
} else {
const evt = new e.constructor(e.type, e);
redirectingElement.dispatchEvent(evt);
}
};
element.addEventListener('mouseup', redirect);
element.addEventListener('mousedown', (e) => e.preventDefault());
element.addEventListener('click', (e) => e.preventDefault());
}
});
}
function handleMenus() {
const menuElements = [
...document.querySelectorAll('.header-menu > a'),
...document.querySelectorAll('.header-menu > p.menu-label')
];
let scrollYMemory = -1;
function scrollToMemory() {
if (scrollYMemory !== -1) {
setTimeout(function() {
window.scrollTo(0, scrollYMemory);
scrollYMemory = -1;
}, 10);
}
}
function hideMenus() {
// unfocus menus & items, and set the menus to non-visible.
menuElements.forEach(function (element) {
element.blur();
element.parentElement.blur();
element.parentElement.classList.remove('menu-opened');
const menuItems = element.parentElement.querySelector('.header-menu-items');
if (menuItems) {
menuItems.style.display = 'none';
}
});
const wasHidden = document.body.style.overflowY === 'hidden';
document.body.style.overflowY = 'auto';
if (wasHidden) {
scrollToMemory();
}
}
function isEventOutsideMenu(e) {
return e.target.closest && !e.target.closest('.header-menu') && !e.target.closest('[data-activate-menu]');
}
// Handle mobile menu toggling
menuElements.forEach(function(element) {
const menu = element.parentElement;
const headerItems = menu.querySelector('.header-menu-items');
const menuEntries = headerItems ? headerItems.querySelectorAll('.header-menu-item') : [];
const focus = function() {
menu.focus();
menu.classList.add('menu-opened');
if (headerItems) {
headerItems.style.display = 'block';
}
if (menu.classList.contains('zone-menu')) {
scrollYMemory = window.scrollY;
document.body.style.overflowY = 'hidden';
} else {
scrollYMemory = -1;
}
};
const unfocus = function() {
menu.blur();
menu.classList.remove('menu-opened');
if (headerItems) {
headerItems.style.display = 'none';
}
const wasHidden = document.body.style.overflowY === 'hidden';
document.body.style.overflowY = 'auto';
if (wasHidden) {
scrollToMemory();
}
};
const toggleMenuVisibility = function(e) {
if ((e.type === 'click' || e.type === 'mouseup') && !isLeftClick(e)) {
e.preventDefault();
return;
}
const hidden = isHidden(headerItems);
if (menu.hasAttribute('data-click-activation')) { // handle click activated toggling
if (hidden) {
hideMenus(); // hide other open menus first
focus();
} else {
unfocus();
}
e.preventDefault();
} else if (hidden) {
hideMenus(); // hide other open menus first
focus();
e.preventDefault(); // prevent 'click' event from firing when menu is hidden
}
};
element.addEventListener('touchend', toggleMenuVisibility);
element.addEventListener('mouseup', toggleMenuVisibility);
// Add hover events to non-click-activated menus, even though CSS should cover it.
if (!menu.hasAttribute('data-click-activation')) {
menu.addEventListener('mouseover', function () {
hideMenus(); // hide other open menus first
focus();
});
menu.addEventListener('mouseout', function (e) {
if (isEventOutsideMenu(e)) {
unfocus();
}
});
}
// Hide menu when child is clicked
menuEntries.forEach(function(menuEntry) {
const linkToItem = function(e) {
if (e.type === 'mousedown' || e.type === 'click') {
e.preventDefault();
return;
}
if (e.type === 'mouseup' && !isLeftClick(e)) {
e.preventDefault();
return;
}
window.open(getLink(menuEntry), (e.ctrlKey || e.metaKey) ? '_blank' : (menuEntry.target || '_self'));
unfocus();
e.preventDefault();
};
const linkToMobileItem = function(e) {
if (e.type === 'touchstart') {
menuEntry.setAttribute('data-touchmove', false);
} else if (e.type === 'touchmove') {
menuEntry.setAttribute('data-touchmove', true);
} else if (e.type === 'touchend' && (!menuEntry.hasAttribute('data-touchmove') || menuEntry.getAttribute('data-touchmove').toLowerCase() === 'false')) {
window.open(getLink(menuEntry), (e.ctrlKey || e.metaKey) ? '_blank' : (menuEntry.target || '_self'));
unfocus();
e.preventDefault();
}
};
menuEntry.addEventListener('mousedown', linkToItem);
menuEntry.addEventListener('mouseup', linkToItem);
menuEntry.addEventListener('click', linkToItem);
menuEntry.addEventListener('touchstart', linkToMobileItem);
menuEntry.addEventListener('touchmove', linkToMobileItem);
menuEntry.addEventListener('touchend', linkToMobileItem);
});
});
function hideIfNonMenuBounds(e) {
if (isEventOutsideMenu(e)) {
hideMenus();
}
}
addEventListener('mousemove', hideIfNonMenuBounds);
addEventListener('touchend', hideIfNonMenuBounds);
addEventListener('mouseup', hideIfNonMenuBounds);
addEventListener('mousedown', hideIfNonMenuBounds);
addEventListener('click', hideIfNonMenuBounds);
}
function handleMobileMenuHeights() {
function setAppHeight() {
document.documentElement.style.setProperty('--app-height', window.innerHeight + 'px');
}
addEventListener('resize', function() {
setAppHeight();
});
setAppHeight();
}
function handleGotoLinks() {
// Add anchor mimicking to elements with data-goto attributes
// This addresses SEO concerns of linking to noindex pages by allowing JS to handle the URL
const anchorElements = document.querySelectorAll('*[data-goto]');
anchorElements.forEach((anchorElement) => {
anchorElement.addEventListener('mouseover', () => {
anchorElement.style.cursor = 'pointer';
anchorElement.style.textDecoration = 'underline';
});
anchorElement.addEventListener('mouseout', () => {
anchorElement.style.cursor = 'unset';
anchorElement.style.textDecoration = 'unset';
});
anchorElement.addEventListener('mouseup', (e) => {
e.preventDefault();
});
anchorElement.addEventListener('mousedown', (e) => {
e.preventDefault();
});
anchorElement.addEventListener('click', (e) => {
e.preventDefault();
const anchorHref = anchorElement.getAttribute('data-goto');
if (anchorElement.hasAttribute('data-new-window') || e.ctrlKey || e.metaKey) {
window.open(anchorHref, '_blank');
} else {
window.open(anchorHref, '_self');
}
});
});
}
</script><script>
const authenticatedBlock = document.querySelector('#authenticated-block');
const unauthenticatedBlock = document.querySelector('#unauthenticated-block');
let authenticated = {
isAuthenticated: false,
isAdmin: false,
user: {
id: null,
name: null,
url: null,
profileImage: null,
}
};
fetch('/services/internal/data/articles-getAuthenticationStatus', {
headers: {
'Accept': 'application/json'
}
})
.then(function (result) {
return result.json()
})
.then(function (result) {
const res = result.result.data
if (!res.authenticated) {
unauthenticatedBlock.classList.add('shown')
} else {
authenticated.user.id = res.id;
authenticated.user.name = res.realName;
authenticated.user.url = res.profileUrl;
authenticated.user.profileImage = res.avatar;
authenticated.user.jobTitle = res.jobTitle;
authenticated.user.companyName = res.companyName;
bindProps('#header-username', null, null, (res.firstName || res.username), null)
bindProps('#header-avatar', null, res.avatar, null, null)
bindProps('#header-user-plug', res.profileUrl, null, res.realName, null)
bindProps('#header-user-edit', '/users/' + res.id + '/edit.html', null, null, null)
bindProps('#header-dropdown-manage-email', '/newsletters/' + res.id + '/manage.html', null, null, null)
bindProps('#dropdown-view-profile', res.profileUrl, null, null, null)
bindProps('#drafts-link', '/users/' + res.id + '/drafts.html', null, null, null)
if (res.isAdmin) {
// Construct backwards so the #after call places elements in the correct order
const firstUserAction = document.querySelector('#first-user-action')
const adminConsoleItem = document.createElement('li')
const adminConsoleLink = createLink('/dzone/staff/index.html', 'Admin Console')
adminConsoleItem.appendChild(adminConsoleLink)
firstUserAction.after(adminConsoleItem)
const moderationItem = document.createElement('li')
const moderationLink = createLink('/moderation/list.html', 'Moderation')
moderationItem.appendChild(moderationLink)
firstUserAction.after(moderationItem)
const bountyModerationItem = document.createElement('li')
const bountyModerationLink = createLink('/moderation/bounties', 'Bounty Moderation')
bountyModerationItem.appendChild(bountyModerationLink)
firstUserAction.after(bountyModerationItem)
}
authenticated.isAuthenticated = res.authenticated;
authenticated.isAdmin = res.isAdmin;
authenticatedBlock.classList.add('shown')
}
}).catch(function (result) {
console.error(result)
})
/**
* Binds different properties to the selected element.
*
* @param selector - Selector to select the element
* @param href - href attribute value
* @param src - src attribute value
* @param innerHTML - innerHTML property value
* @param innerText - innerText property value
*/
function bindProps(selector, href, src, innerHTML, innerText) {
const element = document.querySelector(selector)
if (element) {
if (href) element.href = href
if (src) element.src = src
if (innerHTML) element.innerHTML = innerHTML
if (innerText) element.innerText = innerText
}
}
/**
* Creates a new link element.
*
* @param href - href attribute value
* @param innerText - innerText property value
* @returns {HTMLAnchorElement} The generated link element
*/
function createLink(href, innerText) {
const link = document.createElement('a')
link.href = href
link.innerText = innerText
return link
}
</script><script>
const userHeader = document.querySelector('#user-header')
const userDropdown = document.querySelector('#user-dropdown')
const postDropdown = document.querySelector('#post-button')
const postMenu = document.querySelector('#post-menu')
let userDropdownOpen = false
let postDropdownOpen = false
document.addEventListener('click', function(event) {
if (postDropdown && postDropdown.contains(event.target)) {
setUserDropdown(false)
setPostDropdown(!postDropdownOpen)
} else if (userHeader && userHeader.contains(event.target)) {
setPostDropdown(false)
setUserDropdown(!userDropdownOpen)
} else {
setUserDropdown(false)
setPostDropdown(false)
}
})
function setUserDropdown(value) {
userDropdownOpen = value
if (userDropdownOpen) {
if (userDropdown) {
userDropdown.classList.add('open')
}
} else {
if (userDropdown) {
userDropdown.classList.remove('open')
}
}
}
function setPostDropdown(value) {
postDropdownOpen = value
if (postDropdownOpen) {
if (postMenu) {
postMenu.classList.add('open')
}
} else {
if (postMenu) {
postMenu.classList.remove('open')
}
}
}
</script><script>
document.addEventListener("alpine:init", () => {
Alpine.store('global', {
executeHttp(path, body, method) {
const options = {
method: method,
headers: {
[csrf.header]: csrf.token
}
};
if (method !== 'GET') {
options.body = JSON.stringify(body);
options.headers = {
...options.headers,
'Content-Type': 'application/json; charset=UTF-8'
};
}
return new Promise((resolve, reject) => {
fetch(path, options)
.then(res => {
if (!res.ok) {
reject(res);
} else {
resolve(res);
}
})
.catch(err => reject(err));
});
},
getFromService(path) {
return this.executeHttp(path, null, 'GET');
},
postToService(path, body = {}) {
return this.executeHttp(path, body, 'POST');
},
putToService(path, body = {}) {
return this.executeHttp(path, body, 'PUT');
}
});
});
</script>
<link rel="stylesheet" media="all" href="https://dz2cdn1.dzone.com/themes/dz20/ftl/colors.css">
<link rel="stylesheet" media="all" href="https://dz2cdn1.dzone.com/themes/dz20/ftl/article/styles.css">
<div id="body-container">
<div id="announcement-container-outer">
<div id="announcement-previous">
<i class="icon-angle-left"></i>
</div>
<div id="announcement-next">
<i class="icon-angle-right"></i>
</div>
<div id="announcement-container">
<div class="announcement announcement-count-1"
data-position="1">
<div class="body"><p><strong>Could your team report a vulnerability within 24 hours? </strong>Find out on September 23.</p></div>
<div class="spacer"></div>
<a href="https://cvent.me/O32zXR?utm_source=Announcements&amp;utm_medium=DzoneWeb&amp;utm_campaign=QtGroup-0923" target="_blank">
<button>Assess Your CRA Readiness</button>
</a>
</div>
</div>
</div>
<script type="text/javascript" async>
(function() {
let announcementPosition = 1;
let minAnnouncementPosition = -1;
let maxAnnouncementPosition = -1;
const announcementPrevBtn = document.querySelector('#announcement-previous');
const announcementNextBtn = document.querySelector('#announcement-next');
function withAnnouncements(callback) {
const announcements = document.querySelectorAll('#announcement-container .announcement');
for (let announcement of announcements) {
callback(announcement);
}
}
function initAnnouncementVars() {
document.querySelector(':root').style.setProperty('--mobile-announcement-separator-width', '1px');
withAnnouncements(function(announcement) {
const pos = parseInt(announcement.getAttribute('data-position'));
minAnnouncementPosition = minAnnouncementPosition === -1 ? pos : Math.min(minAnnouncementPosition, pos);
maxAnnouncementPosition = maxAnnouncementPosition === -1 ? pos : Math.max(maxAnnouncementPosition, pos);
});
if (document.querySelector('.announcementBarContainer')) {
document.querySelector(':root').style.setProperty('--body-top-padding', '0');
}
if (maxAnnouncementPosition <= 0) {
document.querySelector(':root').style.setProperty('--body-top-padding', '0');
}
sizeToFullWhenOneEntryOnMobile();
}
function setAnnouncementPosition(position) {
if (window.outerWidth >= 890) {
return; // we do not need to change the position, since we can display everything on desktop.
}
// Make the announcement cyclical
if (minAnnouncementPosition !== -1 && maxAnnouncementPosition !== -1) {
if (position > maxAnnouncementPosition) {
position = minAnnouncementPosition; // overflow to the first announcement
} else if (position < minAnnouncementPosition) {
position = maxAnnouncementPosition; // underflow to the last announcement
}
announcementPosition = position;
}
const shownAnnouncements = [];
let reverseFlex = false; // should only be true when the first and last items are showing
// Now that the position is valid, apply the transforms
withAnnouncements(function(announcement) {
const pos = parseInt(announcement.getAttribute('data-position'));
const isWrapped = position === maxAnnouncementPosition && pos === minAnnouncementPosition; // showing first + last at same time
const doesNextQualify = window.outerWidth >= 500 && (pos === position + 1 || isWrapped);
if (pos === position || doesNextQualify) {
shownAnnouncements.push(announcement);
} else {
announcement.style.display = 'none';
}
announcement.style.opacity = 0.0;
if (isWrapped) {
reverseFlex = true;
}
});
for (let announcement of shownAnnouncements) {
announcement.style.display = 'flex';
announcement.style.opacity = 1.0;
}
const announcementContainer = document.querySelector('#announcement-container');
if (announcementContainer) {
announcementContainer.style.flexDirection = reverseFlex ? 'row-reverse' : 'row';
}
}
function resetAnnouncements() {
announcementPosition = 1;
const announcementContainer = document.querySelector('#announcement-container');
if (announcementContainer) {
announcementContainer.style.flexDirection = 'row';
}
withAnnouncements(function(announcement) {
announcement.style.opacity = 1.0;
announcement.style.display = 'flex';
});
}
function sizeToFullWhenOneEntryOnMobile() {
if (maxAnnouncementPosition <= 2 && announcementPrevBtn && announcementNextBtn) {
announcementPrevBtn.style.display = maxAnnouncementPosition === 2 && window.outerWidth < 500 ? 'flex' : 'none';
announcementNextBtn.style.display = maxAnnouncementPosition === 2 && window.outerWidth < 500 ? 'flex' : 'none';
}
if (maxAnnouncementPosition === 1 && window.outerWidth >= 500 && window.outerWidth < 890) {
document.querySelector(':root').style.setProperty('--mobile-announcement-separator-width', '0');
document.querySelector('#announcement-container .announcement').style.maxWidth = '100%';
}
}
function resetAnnouncementsOnResize() {
announcementPosition = 1; // we want to reset the announcement position every resize
if (window.outerWidth >= 890) { // 4 announcements can be shown (215px * 4) + separator padding
resetAnnouncements();
} else { // if we're still on mobile, set the position to normalize things after resize
setAnnouncementPosition(announcementPosition);
sizeToFullWhenOneEntryOnMobile();
}
}
window.addEventListener('resize', resetAnnouncementsOnResize);
initAnnouncementVars();
setAnnouncementPosition(announcementPosition); // sets the min & max positions as well as initializes transforms
if (announcementPrevBtn) {
announcementPrevBtn.onclick = function() {
setAnnouncementPosition(announcementPosition - 1);
};
}
if (announcementNextBtn) {
announcementNextBtn.onclick = function() {
setAnnouncementPosition(announcementPosition + 1);
};
}
})(); </script>
<div id="ftl-article" class="trending-article-body">
<aside class="trending-sidebar" aria-labelledby="related-sidebar-heading">
<div class="trending">
<h2 id="related-sidebar-heading">Related</h2>
<div class="trending-separator"></div>
<ul>
<li class="item">
<a href="/articles/why-your-stateless-services-are-lying-to-you" class="related-link">Why Your "Stateless" Services Are Lying to You</a>
</li>
<li class="item">
<a href="/articles/transit-gateway" class="related-link">Transit Gateway With Anypoint Platform</a>
</li>
<li class="item">
<a href="/articles/real-time-stock-data-updates-with-websockets-using" class="related-link">Real-Time Stock Data Updates with WebSockets using Ballerina</a>
</li>
<li class="item">
<a href="/articles/load-balancing-minecraft-servers-with-kong-gateway" class="related-link">Load-Balancing Minecraft Servers with Kong Gateway</a>
</li>
</ul>
</div>
</aside>
<div class="container-fluid body trending-article-fluid">
<div class="row">
<div class="col-md-12">
<div class="articles-wrap">
<div class="ad-container">
<div id="div-gpt-ad-1435246566686-0" class="ads-billboard-article dz2_article_billboard_new" data-gpt-slot="top"></div>
</div>
<div class="article-stream widget-top-border">
<div class="article-main">
<div class="content-right-images">
<div id="div-gpt-ad-1435246566686-2" class="sidebar-ad dz2_article_halfpage_new" data-gpt-slot="sidebar1"></div>
<div class="trending-separator"></div>
<aside class="trending" aria-labelledby="trending-sidebar-heading">
<h2 id="trending-sidebar-heading">Trending</h2>
<ul>
<li class="item">
<a href="/articles/resilient-python-api-client" class="trending-link" target="_self">Building a Python API Client That Doesn’t Fall Apart When the API Misbehaves</a>
</li>
<li class="item">
<a href="/articles/email-security-dns" class="trending-link" target="_self">Your Email Security Is a DNS Configuration Problem</a>
</li>
<li class="item">
<a href="/articles/human-ai-agent-detector" class="trending-link" target="_self">Optimize an AI Agent to Sound Human, Judged by an AI Detector</a>
</li>
<li class="item">
<a href="/articles/excel-dynamic-arrays-java" class="trending-link" target="_self">Dynamic Arrays, Spill, and LET: What Changed in Excel and Why It Matters for Java Applications</a>
</li>
</ul>
</aside>
</div>
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "Article",
"headline": "Accelerating Connection Handshakes in Trusted Network Environments",
"author": [{"image":{"@type":"ImageObject","caption":"Maksim Kupriianov","url":"https://secure.gravatar.com/avatar/f9982db1a9aa0a30faa76f87cbbe8cc5?d=identicon&r=PG"},"@type":"Person","worksFor":{"@type":"Organization","name":"Maksim Kupriianov"},"name":"Maksim Kupriianov","url":"https://dzone.com/users/5179298/max-kupriianov.html"}],
"audience": "software developers",
"keywords": "",
"timeRequired": "PT17M",
"commentCount": 4,
"wordCount": "4249",
"accessMode": "textual, visual",
"datePublished": "2024-10-24T00:00:00Z",
"articleSection": "",
"publisher": {
"@type": "Organization",
"name": "DZone",
"url": "https://dzone.com",
"logo": {
"@type": "ImageObject",
"url": "https://dzone.com/themes/dz20/images/dz_logo_2021_cropped.png"
}
},
"articleBody": "In this article, I aim to discuss modern approaches used to reduce the time required to establish a data transmission channel between two nodes. I will be examining both plain TCP and TLS-over-TCP. What Is a Handshake? First, let’s define what a handshake is, and for that, an illustration of the TCP handshake serves very well: The handshake is the process of exchanging messages between two nodes (such as a client and a server) to establish a connection and negotiate communication parameters before data transmission begins. The purpose of a handshake is to: Confirm the readiness of both nodes to communicate.Agree on connection parameters: protocol versions, encryption methods, authentication, and other technical details.Ensure the security and reliability of the connection before transmitting sensitive or important data. The handshake is a critically important step in network interaction because it sets the foundation for subsequent communication, guaranteeing that both nodes \"understand\" each other and can securely exchange information. Thus, no data could be exchanged between communication parties until the handshake is done. Why Is Optimizing the Handshake Time Important? Even if a handshake looks like a very short and simple procedure, it can take a very long time for long-distance communication. It's well known that the maximum distance between any two points on the Earth's surface is about 20,000 km, which is half the Earth’s circumference. If you take into account the speed of light (~300 km/sec), it means that the maximum RTT (round-trip-time) between the two points should be not more than 134ms. Unfortunately, that is not how global networks work. Imagine we have a client in the Netherlands and a server in Australia. The RTT between them in practice will be around 250 ms because the network will send your packets first to the US, then to Singapore, and only after that, deliver them to Australia. The channels are also usually quite loaded which could delay the packets even more. It means that a basic TCP handshake could take, let’s say, 300 ms easily. And this is probably more than the server will spend to generate an answer. To illustrate all that, let me start a simple HTTP server somewhere in Sydney. The client will be an Apache Benchmark utility to generate a lot of sequential requests and to see the resulting timings. $ ab -n 1000 -c 1 http://localhost/ … Concurrency Level: 1 Time taken for tests: 0.087 seconds Complete requests: 1000 Failed requests: 0 Total transferred: 205000 bytes Requests per second: 11506.16 [#/sec] (mean) Time per request: 0.087 [ms] (mean) As we can see, the service is blazingly fast and serves 11.5K RPS (requests per second). Now let’s move the client to Amsterdam and recheck the same service. $ ab -n 1000 -c 1 http://service-in-sydney/ … Concurrency Level: 1 Time taken for tests: 559.284 seconds Complete requests: 1000 Failed requests: 0 Total transferred: 205000 bytes Requests per second: 1.79 [#/sec] (mean) Time per request: 559.284 [ms] (mean) The service is 6.5K times slower! And this is all about the latency between the nodes. Of course, if your servers communicate a lot, they’ll probably have a pool of established connections and the problem won’t be that awful, but even in this situation, you have to reconnect from time to time. Also, no one keeps connections open to every partner service it uses, just the major ones. So, it can show severe delays from time to time. Another approach is to multiplex multiple requests in a single HTTP connection. That also could work great — but for instance, in HTTP/2, a single stream can slow down all the other streams together. So, it is not always the best way to go. Trusted Environments We’ll talk about trusted environments only and this is for a reason. For each of the methods we’ll discuss, I’ll mention the security or privacy issues it has if any. To mitigate them, additional measures should be taken, which could be very complicated. That is why I’ll stick to an environment where no one will send any malicious packets to the services’ endpoints. Consider a VPN or at least a network with good Firewall protection. Local area networks are also fine, but usually handshake time optimizations there are not that beneficial. TCP Fast Open First, we will discuss the base-level optimization, suitable for every TCP-based higher-level protocol: TCP Fast Open. History In 2011, a group of engineers at Google, as part of efforts to improve network performance, published a project proposing an extension to the TCP protocol that allowed clients to add data to the SYN packet of the three-way handshake. In other words, for small requests, clients could include them directly in the very first TCP packet sent to the server and start receiving data one RTT faster. To understand how important this is, remember that companies like Google have numerous data centers on most continents, and the software services operating there need to communicate actively with each other. It is not always possible to stay within a single data center, and network packets often have to cross oceans. The technology was first used within Google itself, and in the same year (2011), the first patches were submitted to the Linux kernel code. After checks and refinements, TCP Fast Open was included in the 3.6 release of the kernel in 2012. Subsequently, over the next few years, the extension was actively tested under real conditions and improved in terms of security and compatibility with devices based on other operating systems. Finally, in December 2014, TFO was standardized and published as RFC 7413. For now, almost all the biggest IT companies use TFO in their data centers. You can find articles about having that from Google, Meta, Cloudflare, Netflix, Amazon, Microsoft, etc. It means the technology works, useful to have, and is quite reliable. How It Works There are a lot of good articles describing how TFO works. My favorite is this one, which clarifies in much detail all the possible scenarios. I will describe it very briefly. First Connection: Explanation of Steps Client sends SYN (TFO Cookie Request): The client initiates a TCP connection by sending a SYN packet with a TFO option indicating a cookie request.No application data is sent at this stage because the client does not have the TFO cookie yet.Server responds with SYN-ACK (Including TFO Cookie): The server replies with a SYN-ACK packet, including a freshly generated TFO cookie in the TFO option.The cookie is a small piece of data used to validate future TFO connections from this client.Client sends ACK: The client acknowledges the receipt of the SYN-ACK by sending an ACK packet, completing the TCP three-way handshake.At this point, the TCP connection is established.Now that the connection is established, the client sends the HTTP request over the TCP connection.The server processes the HTTP request and sends back the HTTP response to the client. Later Connections: Explanation of Steps The client initiates a new TCP connection by sending a SYN packet. The SYN packet includes: TFO cookie obtained from the previous connectionHTTP request data (e.g., GET request) attached to the SYN packet; the size of the payload is limited to MSS and usually varies from 500 bytes to 1KbUpon receiving the SYN packet, the server: Validates the TFO cookie to ensure it is legitimateAccepts the early data (HTTP request) included in the SYN packetThe server sends a SYN-ACK packet back to the client.The server may send early HTTP response data if it processes the request quickly enough. Alternatively, the server may send the HTTP response after the handshake is complete.Client Sends ACK. The Experiment As you can see, all the later connections could save 1 RTT on getting the data. Let’s test it with our Amsterdam/Sydney client/server lab. As to not modify either the client or the server, I’ll add a reverse proxy service on top of the client and the server: System-Wide Configuration Before doing anything, you need to ensure TFO is enabled on your server and the client. To do that on Linux, take a look at the net.ipv4.tcp_fastopen bitmask-parameter. Usually, it will be set by default to one or three. # enabling client and server support $ sudo sysctl -w net.ipv4.tcp_fastopen=3 net.ipv4.tcp_fastopen = 3 According to the kernel documentation, possible bits to enable the parameter are as follows: 0x1 (client) Enables sending data in the opening SYN on the client 0x2 (server) Enables the server support, i.e., allowing data in a SYN packet to be accepted and passed to the application before 3-way handshake finishes 0x4 (client) Send data in the opening SYN regardless of cookie availability and without a cookie option. 0x200 (server) Accept data-in-SYN w/o any cookie option present. 0x400 (server) Enable all listeners to support Fast Open by default without explicit TCP_FASTOPEN socket option. Note the bits 0x4 and 0x200. They allow bypassing the need for the client to have a cookie altogether. This enables accelerating even the first TCP handshake with a previously unknown client, which can be acceptable and useful in a trusted environment. Option 0x400 allows forcing the server code, even if it knows nothing about TFO, to accept data received in the SYN packet. This is generally not safe but may be necessary if modifying the server code is not feasible. Reverse Proxy Configuration After configuring the system, let’s install and configure a reverse proxy. I will use HAProxy for that, but you can choose any. HAProxy configuration on the server host: frontend fe_main bind :8080 tfo # TFO enabled on listening socket default_backend be_main backend be_main mode http server server1 127.0.0.1:8081 # No TFO towards the server HAProxy configuration on the client’s host: frontend fe_main bind 127.0.0.1:8080 # No TFO on listening socket default_backend be_main backend be_main mode http retry-on all-retryable-errors http-request disable-l7-retry if METH_POST server server1 server-in-sydney:8080 tfo # TFO enabled towards the proxy on the server And let’s test again: $ ab -n 1000 -c 1 http://localhost:8080/ … Concurrency Level: 1 Time taken for tests: 280.452 seconds Complete requests: 1000 Failed requests: 0 Total transferred: 205000 bytes Requests per second: 3.57 [#/sec] (mean) Time per request: 280.452 [ms] (mean) As you can see we got a result twice as good as before. And except for the URL, we had nothing to change in the client or the server themselves. The reverse proxy handled all the TFO-related work and that is a great way to go. Downsides of TFO Now the sad part about TFO: The first and biggest issue for TFO is that it is vulnerable to the so-called “replay” attacks. Imagine an intruder who sniffed the process of handshaking for some client and then started to send malicious requests on behalf of the client. Yes, the attacker won’t receive the results of the requests, but it can break something, make an amplification attack toward the client, or just exhaust the server's resources. Another big issue is privacy. In www-world, clients get cookies. We all know how those cookies can be used to track clients. The same applies to TFO, unfortunately. As soon as your device gets a cookie from the server, it will use it for many hours for all the outgoing connections to that server, even if not early data to be sent there. And the cookie becomes the unique ID of your machine. A good paper with the investigation of these security flaws can be found here. Also, even though the technology is quite mature, a lot of network devices still can block and drop SYN packets with payload, which causes issues. That said, TFO is dangerous to use in the wild Internet and it is more suitable for safer internal networks. TLS TLS (Transport Layer Security) is a cryptographic protocol designed to provide end-to-end security for data transmitted over a network. It achieves this by encrypting the data between two communicating applications so eavesdroppers can't intercept or tamper with the information. During the TLS handshake, the client and server agree on encryption algorithms and exchange keys, setting up a secure channel for data transfer. And, yes, there's the \"handshake\" word again. First, let’s take a look at the sequence diagram of a TLS 1.2 connection: Protected HTTP Request: Explanation of Steps TCP SYN: The client initiates a TCP connection by sending a SYN packet to the server.TCP SYN-ACK: The server responds to the client's SYN with a SYN-ACK packet to acknowledge the client's SYN and indicate readiness to establish a connection.TCP ACK: The client acknowledges the server's SYN-ACK by sending an ACK packet to complete the TCP three-way handshake, establishing the TCP connection.ClientHello: The client initiates the TLS handshake by sending a ClientHellomessage over the established TCP connection. It contains the following: Protocol version: Highest TLS version supported (e.g., TLS 1.2)Client random: A random value used in key generationSession ID: For session resumption (may be empty)Cipher suites: List of supported encryption algorithmsCompression methods: Usually null (no compression)Extensions: Optional features (e.g., Server Name Indication)ServerHello: The server responds with a ServerHello message which contains: Protocol version: Selected TLS version (must be ≤ client's version)Server random: Another random value for key generationSession ID: Chosen session ID (matches client's if resuming a session)Cipher suite: Selected encryption algorithm from the client's listCompression method: Selected compression methodExtensions: Optional features agreed uponCertificate: The server sends its X.509 Certificate which contains the server's public key and identity information to authenticate itself to the client. This could be a chain of certificates.ServerKeyExchange (optional): Sent if additional key exchange parameters are needed (e.g., for Diffie-Hellman key exchange)ServerHelloDone: Indicates that the server has finished sending its initial handshake messages and awaits the client's responseClientKeyExchange: The client sends key exchange information to the server to allow both client and server to compute the shared premaster secret.ChangeCipherSpec: The client signals that it will start using the newly negotiated encryption and keys.Finished: The client sends a Finished message encrypted with the new cipher suite. It contains a hash of all previous handshake messages. It allows the server to verify that the handshake integrity is intact and that the client has the correct keys.ChangeCipherSpec: The server signals that it will start using the negotiated encryption and keys.Finished: The server sends a Finished message encrypted with the new cipher suite. It contains a hash of all previous handshake messages and allows the client to verify that the server has successfully completed the handshake and that the keys match.HTTP Request: Now that the connection is established, the client sends the HTTP request over the encrypted TLS connection.HTTP Response: The server processes the HTTP request and sends back the encrypted HTTP response to the client. A big one, isn’t it? If you analyze the diagram, one can find that it is at least 4 RTTs required to establish a connection and to get a response to the request from the client. Let’s check it in our test lab with a well-known curl-utility. The Experiment To get various useful timings from curl, I usually create the following file somewhere in the filesystem of a server: $ cat /var/tmp/curl-format.txt \\n time_namelookup: %{time_namelookup}\\n time_connect: %{time_connect}\\n time_appconnect: %{time_appconnect}\\n time_pretransfer: %{time_pretransfer}\\n time_redirect: %{time_redirect}\\n time_starttransfer: %{time_starttransfer}\\n ----------\\n time_total: %{time_total}\\n \\n size_request: %{size_request}\\n size_upload: %{size_upload}\\n size_header: %{size_header}\\n size_download: %{size_download}\\n speed_upload: %{speed_upload}\\n speed_download: %{speed_download}\\n \\n Now let’s use it (-w parameter) and make a call using TLS 1.2 from Amsterdam to Sydney: $ curl -w \"@/var/tmp/curl-format.txt\" https://server-in-sydney/ --http1.1 --tlsv1.2 --tls-max 1.2 -o /dev/null time_namelookup: 0.001645 time_connect: 0.287356 time_appconnect: 0.867459 time_pretransfer: 0.867599 time_redirect: 0.000000 time_starttransfer: 1.154564 ---------- time_total: 1.154633 size_request: 86 size_upload: 0 size_header: 181 size_download: 33 speed_upload: 0 speed_download: 28 More than a second for a single request! Remember that the service is capable of serving 11.5K requests per second. Let’s try to do something with it. TLSv1.3 This is the newest stable version of the TLS protocol defined in “RFC 8446 - The Transport Layer Security (TLS) Protocol Version 1.3.” And you know what? It is great and absolutely safe to use everywhere you can set it up. The only downside is not every client supports it for now, but servers could be configured to support both TLS 1.2 and 1.3 versions, so it is not a big deal. Now, let’s take a look at what is so great in TLSv1.3 for our handshake optimization. For that, here's a new sequence diagram. Protected HTTP Request: Explanation of Steps TCP SYN: The client initiates a TCP connection by sending a SYN packet to the server.TCP SYN-ACK: The server responds to the client's SYN with a SYN-ACK packet to acknowledge the client's SYN and indicate readiness to establish a connection.TCP ACK: The client acknowledges the server's SYN-ACK by sending an ACK packet to complete the TCP three-way handshake, establishing the TCP connection.ClientHello: The client initiates the TLS handshake by sending a ClientHellomessage. It proposes security parameters and provides key material for key exchange. The message contains: Protocol version: Indicates TLS 1.3Random value: A random number for key generationCipher suites: List of supported cipher suitesKey share extension: Contains the client's ephemeral public key for key exchange (e.g., ECDHE)Supported versions extension: Indicates support for TLS 1.3Other extensions: May include Server Name Indication (SNI), signature algorithms, etc.Function: Proposes security parameters and provides key material for key exchangeServerHello: The server responds with a ServerHellomessage to agree on security parameters and complete the key exchange. Its contents include: Protocol version: Confirms TLS 1.3Random value: Another random number for key generationCipher suite: Selected cipher suite from the client's listKey share extension: Contains the server's ephemeral public keySupported versions extension: Confirms use of TLS 1.3.EncryptedExtensions: The server sends EncryptedExtensions containing extensions that require confidentiality, like server parameters.Certificate: The server provides its certificate and any intermediate certificates to authenticate itself to the client.CertificateVerify: The server proves possession of the private key corresponding to its certificate by signing all the previous handshake messages.Finished: The server signals the completion of its handshake messages with an HMAC over the handshake messages, ensuring integrity.Finished: The client responds with its own Finished message with an HMAC over the handshake messages, using keys derived from the shared secret.HTTP Request: Now that the connection is established, the client sends the HTTP request over the encrypted TLS connection.HTTP Response: The server processes the HTTP request and sends back the encrypted HTTP response to the client. As you can see, TLSv1.3 reduces the handshake to one round trip (1-RTT) compared with the previous version of the protocol. It also allows only ephemeral key exchanges and simplified cipher suites. While it is very good for security, that is not our main concern here. For us, it means less data is required to be sent between the parties. The Experiment Let’s try it out with our curl command: $ curl -w \"@/var/tmp/curl-format.txt\" https://server-in-sydney/ --http1.1 --tlsv1.3 -o /dev/null time_namelookup: 0.003245 time_connect: 0.265230 time_appconnect: 0.533588 time_pretransfer: 0.533673 time_redirect: 0.000000 time_starttransfer: 0.795738 ---------- time_total: 0.795832 size_request: 86 size_upload: 0 size_header: 181 size_download: 33 speed_upload: 0 speed_download: 41 And it is all true: we cut one RTT from the response. Great! Mixing the Things We already cut one RTT by just upgrading the TLS protocol version to 1.3. Let’s remember we have TCP Fast Open available. With that, we can send a ClientHello message directly inside a SYN packet. Will it work? Let’s find out. Curl supports an option to enable TCP Fast Open towards the target. The target still has to support TFO. $ curl -w \"@/var/tmp/curl-format.txt\" https://server-in-sydney/ --http1.1 --tlsv1.3 -o /dev/null --tcp-fastopen % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 100 33 0 33 0 0 57 0 --:--:-- --:--:-- --:--:-- 57 time_namelookup: 0.002215 time_connect: 0.002272 time_appconnect: 0.292444 time_pretransfer: 0.292519 time_redirect: 0.000000 time_starttransfer: 0.574951 ---------- time_total: 0.575200 size_request: 86 size_upload: 0 size_header: 181 size_download: 33 speed_upload: 0 speed_download: 57 And it does! We now halved the original TLSv1.2 timings. But let’s look at one more thing to consider. Zero Round-Trip Time (0-RTT) TLS 1.3 doesn’t only significantly streamline the handshake process, enhancing both security and performance. It also provides a new feature called Zero Round-Trip Time (or 0-RTT). 0-RTT allows a client to start transmitting data to a server immediately, without waiting for the full TLS handshake to complete. For that, a previous TLS connection had to exist and its keys are being reused. Let’s take a look at the sequence diagram: Zero Round-Trip Time Request: Explanation of Steps TCP SYN: The client initiates a TCP connection by sending a SYN packet to the server.TCP SYN-ACK: The server responds to the client's SYN with a SYN-ACK packet to acknowledge the client's SYN and indicate readiness to establish a connection.TCP ACK: The client acknowledges the server's SYN-ACK by sending an ACK packet to complete the TCP three-way handshake, establishing the TCP connection.ClientHello with early data (0-RTT data): The client initiates the handshake and sends the HTTP request as early data (which could be an HTTP request). It contains the following: Protocol version: Indicates support for TLS 1.3Cipher suites: List of supported cipher suitesKey share extension: Contains the client's ephemeral public key for key exchange (e.g., ECDHE)Pre-shared key (PSK): Includes a session ticket or PSK obtained from a previous connectionEarly data indication: Signals the intention to send 0-RTT dataEarly data (0-RTT Data): Application data (e.g., HTTP request) sent immediately, encrypted using keys derived from the PSKThe server analyzes the early data and can pass it to a backend for processing.ServerHello: The server responds with its ServerHello, agreeing on protocol parameters. The response contains the following: Protocol version: Confirms TLS 1.3Cipher suite: Selected cipher suite from the client's listKey share extension: Server's ephemeral public keyPre-shared key extension: Indicates acceptance of the PSKEarly data indication (optional): Confirms acceptance or rejection of 0-RTT dataEncryptedExtensions: The server sends additional handshake parameters securely: ALPN, supported groups, etc.Early data indication (optional): Officially accepts or rejects the early data.Finished: The server signals the completion of its handshake messages with an HMAC over the handshake messages to ensure integrity.Finished: The client completes the handshake with an HMAC over the handshake messages.HTTP Response: The server responds to the request received as early data. No complete RTT saving here, but the request is being transferred as soon as possible. It gives more time to the server to process it and there are more chances that the response will be sent out right after the handshake completes. That is also a great thing. Unfortunately, such an approach reduces the security as by TLS 1.3 design, session keys should not be reused at all. Also, it opens a surface for replay attacks. I’d suggest not using such a technology in a non-secure network environment unless you carefully implement security measures to compensate for those risks. The Experiment That Didn’t Happen I wasn’t able to make an experiment as my service is a very fast responding one and doesn’t benefit from 0-RTT. But if you want to test it yourself, mind that at the moment of preparing this article, curl didn’t support early data. But you can do the testing with “openssl s_client” utility. This is how it can be done: Save TLS-session to the disk with the following command: openssl s_client -connect example.com:443 -tls1_3 -sess_out /tmp/session.pem &lt; /dev/null Create a file with the request: echo -e \"GET / HTTP/1.1\\r\\nHost: example.com\\r\\n\\r\\n\" &gt; /tmp/request.txt Use the saved session to query the server: openssl s_client -connect example.com:443 -tls1_3 -sess_in /tmp/session.pem -early_data /tmp/request.txt -trace HTTP/3 And the last thing I wanted to talk about is HTTP/3. First, it doesn’t use TCP and thus has no requirements for TCP handshake to happen. Second, it supports the same early data approach we’ve just seen in 0-RTT. Lastly, all the congestion and retransmission control is now outside of your kernel and clearly depends on how the server’s and the client’s developers built it. It means that for the story of handshake latency, what you get are still 2 RTTs as TLSv1.3 + TFO provided, but with that, a new high-level protocol encapsulated in UDP. But give it a try; maybe it can help. I will give a brief sequence diagram for HTTP/3, but without deep details, as they are very close to what we’ve seen for TLS1.3 and 0-RTT: Conclusion We took a dive into four available techniques intended to decrease the time spent on handshaking between a client and a server. The handshakes appear at TCP and TLS levels. The common idea for them is to send the data to the server as early as possible. Let’s recap: TCP FastOpen allows saving of one RTT putting the request into the SYN-packet. It is not very safe outside as it is prone to replay attacks, but stable and good to use in protected network environments. Be cautious for non-idempotent services: better to combine with TLS.TLSv1.3 is a newer version of the TLS protocol. It saves one RTT by reducing the amount of information exchanged during the handshake, is very safe, and great to be used inside and outside of trusted networks. It could be combined with TFO to save two RTTs instead of one.Zero Round-Trip Time (0-RTT) is an extension for TLSv1.3. It allows to send a client’s request very soon; thus, giving the server more time to process it. It comes with some security concerns but is mostly safe to use inside of a trusted network perimeter for idempotent services.HTTP/3 is the newest version of the HTTP protocol. It uses UDP and thus has no need for a TCP handshake. It can give you a response after two RTTs, so similar to TLSv1.3 + TFO. I hope this article will be useful to make your service slightly faster. Thanks for reading!",
"mainEntityOfPage": {
"@type": "WebPage",
"@id": "https://dzone.com/articles/accelerating-connection-handshakes"
},
"image": {
"@type": "ImageObject",
"url": "https://dz2cdn1.dzone.com/storage/article-thumb/18002355-thumb.jpg"
}
}
</script>
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "BreadcrumbList",
"itemListElement": [
{
"@type": "ListItem",
"position": 1,
"name": "DZone",
"item": "https://dzone.com"
},
{
"@type": "ListItem",
"position": 2,
"name": "Software Design and Architecture",
"item": "https://dzone.com/software-design-and-architecture"
},
{
"@type": "ListItem",
"position": 3,
"name": "Performance",
"item": "https://dzone.com/performance"
},
{
"@type": "ListItem",
"position": 4,
"name": "Accelerating Connection Handshakes in Trusted Network Environments",
"item": "https://dzone.com/articles/accelerating-connection-handshakes"
}
]
}
</script>
<article>
<div class="content">
<div class="header">
<ol class="breadcrumb">
<li class="server"><a href="https://dzone.com">DZone</a></li>
<li class="server"><a href="https://dzone.com/software-design-and-architecture">Software Design and Architecture</a></li>
<li class="server"><a href="https://dzone.com/performance">Performance</a></li>
<li class="active server">Accelerating Connection Handshakes in Trusted Network Environments</li>
</ol>
<div class="header-title">
<div class="title">
<h1 class="article-title">Accelerating Connection Handshakes in Trusted Network Environments</h1>
</div>
<div class="subhead">
<p>Follow an overview of methods like TCP FastOpen, TLSv1.3, 0-RTT, and HTTP/3 to reduce handshake delays and improve server response times in secure environments.</p>
</div>
<div class="publish-meta">
By<span style="user-select: none;">&nbsp;</span>
<div class="article-author-meta">
<img src="https://secure.gravatar.com/avatar/f9982db1a9aa0a30faa76f87cbbe8cc5?d=identicon&r=PG" class="avatar" alt="Maksim Kupriianov user avatar" width="40">
<div class="author-info">
<span class="author-name">
<a href="/users/5179298/max-kupriianov.html" rel="nofollow" data-core-user="false">Maksim Kupriianov</a>
</span>
</div>
&middot;
</div>
<span class="author-date">
Oct. 24, 24
</span>
&middot;
<span>Tutorial</span>
</div>
</div>
</div>
<div class="author-n-useraction">
<div class="like action" x-data="engagementModal(articleId)">
<span id="activity-like-icon" class="dz-like icon-thumbs-up" x-on:click="updateStore()"></span>
<span class="action-label like-text" x-on:click="open()" x-cloak>
<span>Likes</span>
<span id="activity-like-counter" class="like-count">(20)</span>
</span>
<template x-teleport="body">
<div class="engagement-overlay" x-show="$store.article.engagement.open && nodeId === $store.article.id" x-on:keyup.escape.window="close()">
<div class="engagement-modal">
<div class="inner" x-on:click.outside="close()">
<div class="header">
<div class="title">Likes</div>
<button class="close" x-on:click="close()"></button>
</div>
<div class="content">
<div x-show="$store.article.engagement.initializing" class="loading-screen">
<i class="icon icon-spin3 anim-spin"></i>
</div>
<div x-show="!$store.article.engagement.initializing">
<template x-for="user in $store.article.engagement.users" :key="user.id">
<div class="media">
<div class="media-left">
<img class="avatar"
x-bind:src="user.profile.profileImage"
loading="lazy"
width="50"
height="50"
>
</div>
<div class="media-right">
<a x-bind:href="user.computed.url" x-html="user.profile.name"></a>
<div x-show="formatJobData(user)" x-html="formatJobData(user)"></div>
</div>
</div>
</template>
</div>
<div x-show="isDataUnavailable()" class="center-screen">
<div x-show="!$store.article.engagement.unauthorized">
<div>There are no likes...yet! &#128064;</div>
<div>Be the first to like this post!</div>
</div>
<div x-show="$store.article.engagement.unauthorized">
<div>It looks like you're not logged in.</div>
<div><a href="/users/login.html">Sign in</a> to see who liked this post!</div>
</div>
</div>
<div x-show="isFooterShowing()">
<div class="footer">
<button class="btn btn-primary" x-on:click="fetchEngagements()"
x-bind:disabled="$store.article.engagement.loading"
>
Load More
<i x-show="$store.article.engagement.loading" class="icon icon-spin3 anim-spin"></i>
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</template> </div>
<div class="action comment-action">
<span class="comment">
<i class="icon-comment"></i>
<span class="action-label">Comment</span>
<span id="activity-comment-counter" class="comment-count"></span>
</span>
</div>
<div class="save action">
<div id="activity-save-icon" class="save icon-star-empty">
<span id="activity-save-text" class="action-label">Save</span>
</div>
</div>
<div class="tweet action">
<a id="tweet-link" href="" class="title" target="_blank">
<span><i class="icon-twitter"></i></span>
<span class="action-label">Tweet</span>
</a>
</div>
<div class="linkedin action">
<a id="linkedin-link" href="https://www.linkedin.com/sharing/share-offsite/?url=https://dzone.com/articles/accelerating-connection-handshakes" class="title" target="_blank">
<span><i class="icon-linkedin-1"></i></span>
<span class="action-label">Share</span>
</a>
</div>
<div class="right-most">
<div id="activity-view-container" class="article-views action">
<i class="icon-eye"></i>
<span class="action-label view-count">19.7K Views</span>
</div>
</div>
</div>
<div class="signin-prompt">
<p>Join the DZone community and get the full member experience.</p>
<a id="article-signin-prompt" href="/static/registration.html">Join For Free</a>
</div>
<div class="arrow-down"></div>
<div id="top-bumper-container"></div>
<div>
<div class="content-html"><p dir="ltr">In this article, I aim to discuss modern approaches used to reduce the time required to establish a data transmission channel between two nodes. I will be examining both plain TCP and TLS-over-TCP.&nbsp;</p>
<h2 dir="ltr">What Is a Handshake?</h2>
<p dir="ltr">First, let’s define what a handshake is, and for that, an illustration of the TCP handshake serves very well:</p>
<p dir="ltr"><img alt="sequenceDiagram box White TCP Three-way Connection Handshake participant Client participant Server end Client->>Server: Connection request (SYN-flag packet) Server->>Client: Connection allowed (SYN+ACK flags packet) Client->>Server: Connection established (ACK flag packet) " title="mermaid-graph/neutral" width="648" class="fr-fic fr-dib lazyload" data-image="true" data-new="false" data-sizeformatted="79.1 kB" data-mimetype="image/png" data-creationdate="1729538200393" data-creationdateformatted="10/21/2024 07:16 PM" data-type="temp" data-url="https://dz2cdn1.dzone.com/storage/temp/17994503-1729538199513.png" data-modificationdate="null" data-size="79081" data-name="1729538199513.png" data-id="17994503" data-src="https://dz2cdn1.dzone.com/storage/temp/17994503-1729538199513.png" style="width: 808px; height: 431.432px;"></p>
<p dir="ltr">The handshake is the process of exchanging messages between two nodes (such as a client and a server) to establish a connection and negotiate communication parameters before data transmission begins. The purpose of a handshake is to:</p>
<ul>
<li dir="ltr">Confirm the readiness of both nodes to communicate.</li>
<li dir="ltr">Agree on connection parameters: protocol versions, <a href="https://dzone.com/articles/what-is-encryption-and-how-does-it-work">encryption</a> methods, authentication, and other technical details.</li>
<li dir="ltr">Ensure the security and reliability of the connection before transmitting sensitive or important data.</li>
</ul>
<p dir="ltr">The handshake is a critically important step in network interaction because it sets the foundation for subsequent communication, guaranteeing that both nodes "understand" each other and can securely exchange information.</p>
<p dir="ltr">Thus, no data could be exchanged between communication parties until the handshake is done.</p>
<h3 dir="ltr">Why Is Optimizing the Handshake Time Important?</h3>
<p dir="ltr">Even if a handshake looks like a very short and simple procedure, it can take a very long time for long-distance communication. It's well known that the maximum distance between any two points on the Earth's surface is about 20,000 km, which is half the Earth’s circumference. If you take into account the speed of light (~300 km/sec), it means that the maximum RTT (round-trip-time) between the two points should be not more than 134ms. Unfortunately, that is not how global networks work. Imagine we have a client in the Netherlands and a server in Australia. The RTT between them in practice will be around 250 ms because the network will send your packets first to the US, then to Singapore, and only after that, deliver them to Australia. The channels are also usually quite loaded which could delay the packets even more. It means that a basic TCP handshake could take, let’s say, 300 ms easily. And this is probably more than the server will spend to generate an answer.</p>
<p dir="ltr">To illustrate all that, let me start a simple HTTP server somewhere in Sydney. The client will be an Apache Benchmark utility to generate a lot of sequential requests and to see the resulting timings.</p>
<div class="codeMirror-wrapper" contenteditable="false">
<div contenteditable="false">
<div class="codeHeader">
<i class="icon-cancel-circled-1 cm-remove">&nbsp;</i>
</div>
<div class="codeMirror-code--wrapper" data-code="$ ab -n 1000 -c 1 http://localhost/
…
Concurrency Level: &nbsp; &nbsp; &nbsp;1
Time taken for tests: &nbsp; 0.087 seconds
Complete requests: &nbsp; &nbsp; &nbsp;1000
Failed requests: &nbsp; &nbsp; &nbsp; &nbsp;0
Total transferred: &nbsp; &nbsp; &nbsp;205000 bytes
Requests per second: &nbsp; &nbsp;11506.16 [#/sec] (mean)
Time per request: &nbsp; &nbsp; &nbsp; 0.087 [ms] (mean)" data-lang="">
<pre><code lang="">$ ab -n 1000 -c 1 http://localhost/
…
Concurrency Level: &nbsp; &nbsp; &nbsp;1
Time taken for tests: &nbsp; 0.087 seconds
Complete requests: &nbsp; &nbsp; &nbsp;1000
Failed requests: &nbsp; &nbsp; &nbsp; &nbsp;0
Total transferred: &nbsp; &nbsp; &nbsp;205000 bytes
Requests per second: &nbsp; &nbsp;11506.16 [#/sec] (mean)
Time per request: &nbsp; &nbsp; &nbsp; 0.087 [ms] (mean)</code></pre>
</div>
</div>
</div>
<p><br></p>
<p dir="ltr">As we can see, the service is blazingly fast and serves 11.5K RPS (requests per second). Now let’s move the client to Amsterdam and recheck the same service.</p>
<div class="codeMirror-wrapper" contenteditable="false">
<div contenteditable="false">
<div class="codeHeader">
<i class="icon-cancel-circled-1 cm-remove">&nbsp;</i>
</div>
<div class="codeMirror-code--wrapper" data-code="$ ab -n 1000 -c 1 http://service-in-sydney/
…
Concurrency Level: &nbsp; &nbsp; &nbsp;1
Time taken for tests: &nbsp; 559.284 seconds
Complete requests: &nbsp; &nbsp; &nbsp;1000
Failed requests: &nbsp; &nbsp; &nbsp; &nbsp;0
Total transferred: &nbsp; &nbsp; &nbsp;205000 bytes
Requests per second: &nbsp; &nbsp;1.79 [#/sec] (mean)
Time per request: &nbsp; &nbsp; &nbsp; 559.284 [ms] (mean)" data-lang="">
<pre><code lang="">$ ab -n 1000 -c 1 http://service-in-sydney/
…
Concurrency Level: &nbsp; &nbsp; &nbsp;1
Time taken for tests: &nbsp; 559.284 seconds
Complete requests: &nbsp; &nbsp; &nbsp;1000
Failed requests: &nbsp; &nbsp; &nbsp; &nbsp;0
Total transferred: &nbsp; &nbsp; &nbsp;205000 bytes
Requests per second: &nbsp; &nbsp;1.79 [#/sec] (mean)
Time per request: &nbsp; &nbsp; &nbsp; 559.284 [ms] (mean)</code></pre>
</div>
</div>
</div>
<p><br></p>
<p dir="ltr">The service is 6.5K times slower! And this is all about the latency between the nodes.</p>
<p dir="ltr">Of course, if your servers communicate a lot, they’ll probably have a pool of established connections and the problem won’t be that awful, but even in this situation, you have to reconnect from time to time. Also, no one keeps connections open to every partner service it uses, just the major ones. So, it can show severe delays from time to time.</p>
<p dir="ltr">Another approach is to multiplex multiple requests in a single HTTP connection. That also could work great — but for instance, in HTTP/2, a single stream can slow down all the other streams together. So, it is not always the best way to go.</p>
<h3 dir="ltr">Trusted Environments</h3>
<p dir="ltr">We’ll talk about trusted environments only and this is for a reason. For each of the methods we’ll discuss, I’ll mention the security or privacy issues it has if any. To mitigate them, additional measures should be taken, which could be very complicated. That is why I’ll stick to an environment where no one will send any malicious packets to the services’ endpoints. Consider a VPN or at least a network with good Firewall protection. Local area networks are also fine, but usually handshake time optimizations there are not that beneficial.</p>
<h2 dir="ltr">TCP Fast Open</h2>
<p dir="ltr">First, we will discuss the base-level optimization, suitable for every TCP-based higher-level protocol: TCP Fast Open.&nbsp;</p>
<h3 dir="ltr">History</h3>
<p dir="ltr">In 2011, a group of engineers at Google, as part of efforts to improve network performance, published a project proposing an extension to the TCP protocol that allowed clients to add data to the SYN packet of the three-way handshake. In other words, for small requests, clients could include them directly in the very first TCP packet sent to the server and start receiving data one RTT faster. To understand how important this is, remember that companies like Google have numerous data centers on most continents, and the software services operating there need to communicate actively with each other. It is not always possible to stay within a single data center, and network packets often have to cross oceans.</p>
<p dir="ltr">The technology was first used within Google itself, and in the same year (2011), the first patches were submitted to the Linux kernel code. After checks and refinements, TCP Fast Open was included in the 3.6 release of the kernel in 2012. Subsequently, over the next few years, the extension was actively tested under real conditions and improved in terms of security and compatibility with devices based on other operating systems. Finally, in December 2014, TFO was standardized and published as RFC 7413.</p>
<p dir="ltr">For now, almost all the biggest IT companies use TFO in their data centers. You can find articles about having that from Google, Meta, Cloudflare, Netflix, Amazon, Microsoft, etc. It means the technology works, useful to have, and is quite reliable.</p>
<h3 dir="ltr">How It Works</h3>
<p dir="ltr">There are a lot of good articles describing how TFO works. My favorite is <a href="https://www.qacafe.com/resources/what-is-tcp-fast-open/" rel="noopener noreferrer" target="_blank">this one</a>, which clarifies in much detail all the possible scenarios. I will describe it very briefly.</p>
<p dir="ltr"><img style="width: 796px;" class="fr-fic fr-dib lazyload" data-image="true" data-new="false" data-sizeformatted="29.8 kB" data-mimetype="image/png" data-creationdate="1729538488247" data-creationdateformatted="10/21/2024 07:21 PM" data-type="temp" data-url="https://dz2cdn1.dzone.com/storage/temp/17994507-letter.png" data-modificationdate="null" data-size="29830" data-name="letter.png" data-id="17994507" data-src="https://dz2cdn1.dzone.com/storage/temp/17994507-letter.png" alt="TCP FastOpen (First Connection)"></p>
<h4 dir="ltr">First Connection: Explanation of Steps</h4>
<ol>
<li dir="ltr">Client sends SYN (TFO Cookie Request):
<ul>
<li dir="ltr">The client initiates a TCP connection by sending a SYN packet with a TFO option indicating a cookie request.</li>
<li dir="ltr">No application data is sent at this stage because the client does not have the TFO cookie yet.</li>
</ul></li>
<li dir="ltr">Server responds with SYN-ACK (Including TFO Cookie):
<ul>
<li dir="ltr">The server replies with a SYN-ACK packet, including a freshly generated TFO cookie in the TFO option.</li>
<li dir="ltr">The cookie is a small piece of data used to validate future TFO connections from this client.</li>
</ul></li>
<li dir="ltr">Client sends ACK:
<ul>
<li dir="ltr">The client acknowledges the receipt of the SYN-ACK by sending an ACK packet, completing the TCP three-way handshake.</li>
<li dir="ltr">At this point, the TCP connection is established.</li>
</ul></li>
<li dir="ltr">Now that the connection is established, the client sends the HTTP request over the TCP connection.</li>
<li dir="ltr">The server processes the HTTP request and sends back the HTTP response to the client.</li>
</ol>
<p dir="ltr"><img style="width: 743px;" class="fr-fic fr-dib lazyload" data-image="true" data-new="false" data-sizeformatted="27.9 kB" data-mimetype="image/png" data-creationdate="1729538597095" data-creationdateformatted="10/21/2024 07:23 PM" data-type="temp" data-url="https://dz2cdn1.dzone.com/storage/temp/17994509-2.png" data-modificationdate="null" data-size="27880" data-name="2.png" data-id="17994509" data-src="https://dz2cdn1.dzone.com/storage/temp/17994509-2.png" alt="TCP Fast Open (Later Connections)"></p>
<h4 dir="ltr">Later Connections: Explanation of Steps</h4>
<ol>
<li dir="ltr">The client initiates a new TCP connection by sending a SYN packet. The SYN packet includes:
<ul>
<li dir="ltr">TFO cookie obtained from the previous connection</li>
<li dir="ltr">HTTP request data (e.g., GET request) attached to the SYN packet; the size of the payload is limited to MSS and usually varies from 500 bytes to 1Kb</li>
</ul></li>
<li dir="ltr">Upon receiving the SYN packet, the server:
<ul>
<li dir="ltr">Validates the TFO cookie to ensure it is legitimate</li>
<li dir="ltr">Accepts the early data (HTTP request) included in the SYN packet</li>
<li dir="ltr">The server sends a SYN-ACK packet back to the client.</li>
</ul></li>
<li dir="ltr">The server may send early HTTP response data if it processes the request quickly enough. Alternatively, the server may send the HTTP response after the handshake is complete.</li>
<li dir="ltr">Client Sends ACK.</li>
</ol>
<h3 dir="ltr">The Experiment</h3>
<p dir="ltr">As you can see, all the later connections could save 1 RTT on getting the data. Let’s test it with our Amsterdam/Sydney client/server lab. As to not modify either the client or the server, I’ll add a reverse proxy service on top of the client and the server:</p>
<p dir="ltr"><img style="width: 808px;" class="fr-fic fr-dib lazyload" data-image="true" data-new="false" data-sizeformatted="44.5 kB" data-mimetype="image/png" data-creationdate="1729538712787" data-creationdateformatted="10/21/2024 07:25 PM" data-type="temp" data-url="https://dz2cdn1.dzone.com/storage/temp/17994510-3.png" data-modificationdate="null" data-size="44504" data-name="3.png" data-id="17994510" data-src="https://dz2cdn1.dzone.com/storage/temp/17994510-3.png" alt="Experiment"></p>
<h3 dir="ltr">System-Wide Configuration</h3>
<p dir="ltr">Before doing anything, you need to ensure TFO is enabled on your server and the client. To do that on Linux, take a look at the <code>net.ipv4.tcp_fastopen bitmask-parameter</code>. Usually, it will be set by default to one or three.</p>
<div class="codeMirror-wrapper" contenteditable="false">
<div contenteditable="false">
<div class="codeHeader">
<i class="icon-cancel-circled-1 cm-remove">&nbsp;</i>
</div>
<div class="codeMirror-code--wrapper" data-code="# enabling client and server support
$ sudo sysctl -w net.ipv4.tcp_fastopen=3
net.ipv4.tcp_fastopen = 3" data-lang="">
<pre><code lang=""># enabling client and server support
$ sudo sysctl -w net.ipv4.tcp_fastopen=3
net.ipv4.tcp_fastopen = 3</code></pre>
</div>
</div>
</div>
<p><br></p>
<p>According to the <a href="https://github.com/torvalds/linux/blob/3efc57369a0ce8f76bf0804f7e673982384e4ac9/Documentation/networking/ip-sysctl.rst#L841" rel="noopener noreferrer" target="_blank">kernel documentation</a>, possible bits to enable the parameter are as follows:</p>
<div align="left" dir="ltr">
<div class="table-responsive" style="border: none;">
<table style="max-width: 100%; width: auto; table-layout: fixed; display: table;" width="auto">
<tbody>
<tr style="overflow-wrap: break-word; width: auto;" width="auto">
<td style="overflow-wrap: break-word; width: auto;" width="auto">
<p dir="ltr">0x1</p></td>
<td style="overflow-wrap: break-word; width: auto;" width="auto">
<p dir="ltr">(client)</p></td>
<td style="overflow-wrap: break-word; width: auto;" width="auto">
<p dir="ltr">Enables sending data in the opening SYN on the client</p></td>
</tr>
<tr style="overflow-wrap: break-word; width: auto;" width="auto">
<td style="overflow-wrap: break-word; width: auto;" width="auto">
<p dir="ltr">0x2</p></td>
<td style="overflow-wrap: break-word; width: auto;" width="auto">
<p dir="ltr">(server)</p></td>
<td style="overflow-wrap: break-word; width: auto;" width="auto">
<p dir="ltr">Enables the server support, i.e., allowing data in a SYN packet to be accepted and passed to the application before 3-way handshake finishes</p></td>
</tr>
<tr style="overflow-wrap: break-word; width: auto;" width="auto">
<td style="overflow-wrap: break-word; width: auto;" width="auto">
<p dir="ltr">0x4</p></td>
<td style="overflow-wrap: break-word; width: auto;" width="auto">
<p dir="ltr">(client)</p></td>
<td style="overflow-wrap: break-word; width: auto;" width="auto">
<p dir="ltr">Send data in the opening SYN regardless of cookie availability and without a cookie option.</p></td>
</tr>
<tr style="overflow-wrap: break-word; width: auto;" width="auto">
<td style="overflow-wrap: break-word; width: auto;" width="auto">
<p dir="ltr">0x200</p></td>
<td style="overflow-wrap: break-word; width: auto;" width="auto">
<p dir="ltr">(server)</p></td>
<td style="overflow-wrap: break-word; width: auto;" width="auto">
<p dir="ltr">Accept data-in-SYN w/o any cookie option present.</p></td>
</tr>
<tr style="overflow-wrap: break-word; width: auto;" width="auto">
<td style="overflow-wrap: break-word; width: auto;" width="auto">
<p dir="ltr">0x400</p></td>
<td style="overflow-wrap: break-word; width: auto;" width="auto">
<p dir="ltr">(server)</p></td>
<td style="overflow-wrap: break-word; width: auto;" width="auto">
<p dir="ltr">Enable all listeners to support Fast Open by default without explicit TCP_FASTOPEN socket option.</p></td>
</tr>
</tbody>
</table>
</div>
</div>
<p dir="ltr">Note the bits 0x4 and 0x200. They allow bypassing the need for the client to have a cookie altogether. This enables accelerating even the first TCP handshake with a previously unknown client, which can be acceptable and useful in a trusted environment.</p>
<p dir="ltr">Option 0x400 allows forcing the server code, even if it knows nothing about TFO, to accept data received in the SYN packet. This is generally not safe but may be necessary if modifying the server code is not feasible.</p>
<h3 dir="ltr">Reverse Proxy Configuration</h3>
<p dir="ltr">After configuring the system, let’s install and configure a reverse proxy. I will use <a href="https://dzone.com/articles/how-to-configure-ha-proxy-as-a-proxy-and-loadbalan">HAProxy</a> for that, but you can choose any.</p>
<p dir="ltr">HAProxy configuration on the server host:</p>
<div class="codeMirror-wrapper" contenteditable="false">
<div contenteditable="false">
<div class="codeHeader">
<i class="icon-cancel-circled-1 cm-remove">&nbsp;</i>
</div>
<div class="codeMirror-code--wrapper" data-code="frontend fe_main
&nbsp; &nbsp; bind :8080 tfo # TFO enabled on listening socket
&nbsp; &nbsp; default_backend be_main
backend be_main
&nbsp; &nbsp; mode http
&nbsp; &nbsp; server server1 127.0.0.1:8081 # No TFO towards the server" data-lang="">
<pre><code lang="">frontend fe_main
&nbsp; &nbsp; bind :8080 tfo # TFO enabled on listening socket
&nbsp; &nbsp; default_backend be_main
backend be_main
&nbsp; &nbsp; mode http
&nbsp; &nbsp; server server1 127.0.0.1:8081 # No TFO towards the server</code></pre>
</div>
</div>
</div>
<p dir="ltr"><br></p>
<p dir="ltr">HAProxy configuration on the client’s host:</p>
<div class="codeMirror-wrapper" contenteditable="false">
<div contenteditable="false">
<div class="codeHeader">
<i class="icon-cancel-circled-1 cm-remove">&nbsp;</i>
</div>
<div class="codeMirror-code--wrapper" data-code="frontend fe_main
&nbsp; &nbsp; bind 127.0.0.1:8080 # No TFO on listening socket
&nbsp; &nbsp; default_backend be_main
backend be_main
&nbsp; &nbsp; mode http
&nbsp; &nbsp; retry-on all-retryable-errors
&nbsp; &nbsp; http-request disable-l7-retry if METH_POST
&nbsp; &nbsp; server server1 server-in-sydney:8080 tfo # TFO enabled towards the proxy on the server" data-lang="">
<pre><code lang="">frontend fe_main
&nbsp; &nbsp; bind 127.0.0.1:8080 # No TFO on listening socket
&nbsp; &nbsp; default_backend be_main
backend be_main
&nbsp; &nbsp; mode http
&nbsp; &nbsp; retry-on all-retryable-errors
&nbsp; &nbsp; http-request disable-l7-retry if METH_POST
&nbsp; &nbsp; server server1 server-in-sydney:8080 tfo # TFO enabled towards the proxy on the server</code></pre>
</div>
</div>
</div>
<p><br></p>
<p dir="ltr">And let’s test again:</p>
<div class="codeMirror-wrapper" contenteditable="false">
<div contenteditable="false">
<div class="codeHeader">
<i class="icon-cancel-circled-1 cm-remove">&nbsp;</i>
</div>
<div class="codeMirror-code--wrapper" data-code="$ ab -n 1000 -c 1 http://localhost:8080/
…
Concurrency Level: &nbsp; &nbsp; &nbsp;1
Time taken for tests: &nbsp; 280.452 seconds
Complete requests: &nbsp; &nbsp; &nbsp;1000
Failed requests: &nbsp; &nbsp; &nbsp; &nbsp;0
Total transferred: &nbsp; &nbsp; &nbsp;205000 bytes
Requests per second: &nbsp; &nbsp;3.57 [#/sec] (mean)
Time per request: &nbsp; &nbsp; &nbsp; 280.452 [ms] (mean)" data-lang="">
<pre><code lang="">$ ab -n 1000 -c 1 http://localhost:8080/
…
Concurrency Level: &nbsp; &nbsp; &nbsp;1
Time taken for tests: &nbsp; 280.452 seconds
Complete requests: &nbsp; &nbsp; &nbsp;1000
Failed requests: &nbsp; &nbsp; &nbsp; &nbsp;0
Total transferred: &nbsp; &nbsp; &nbsp;205000 bytes
Requests per second: &nbsp; &nbsp;3.57 [#/sec] (mean)
Time per request: &nbsp; &nbsp; &nbsp; 280.452 [ms] (mean)</code></pre>
</div>
</div>
</div>
<p dir="ltr"><br></p>
<p dir="ltr">As you can see we got a result twice as good as before. And except for the URL, we had nothing to change in the client or the server themselves. The reverse proxy handled all the TFO-related work and that is a great way to go.</p>
<h3 dir="ltr">Downsides of TFO</h3>
<p dir="ltr">Now the sad part about TFO:</p>
<p>The first and biggest issue for TFO is that it is vulnerable to the so-called “replay” attacks. Imagine an intruder who sniffed the process of handshaking for some client and then started to send malicious requests on behalf of the client. Yes, the attacker won’t receive the results of the requests, but it can break something, make an amplification attack toward the client, or just exhaust the server's resources.</p>
<p>Another big issue is privacy. In www-world, clients get cookies. We all know how those cookies can be used to track clients. The same applies to TFO, unfortunately. As soon as your device gets a cookie from the server, it will use it for many hours for all the outgoing connections to that server, even if not early data to be sent there. And the cookie becomes the unique ID of your machine. A good paper with the investigation of these security flaws can be found <a href="https://arxiv.org/pdf/1905.03518" rel="noopener noreferrer" target="_blank">here</a>.</p>
<p>Also, even though the technology is quite mature, a lot of network devices still can block and drop SYN packets with payload, which causes issues.</p>
<p>That said, TFO is dangerous to use in the wild Internet and it is more suitable for safer internal networks.</p>
<h2 dir="ltr">TLS</h2>
<p dir="ltr">TLS (Transport Layer Security) is a cryptographic protocol designed to provide end-to-end security for data transmitted over a network. It achieves this by encrypting the data between two communicating applications so eavesdroppers can't intercept or tamper with the information. During the TLS handshake, the client and server agree on encryption algorithms and exchange keys, setting up a secure channel for data transfer.</p>
<p>And, yes, there's the "handshake" word again. First, let’s take a look at the sequence diagram of a TLS 1.2 connection:</p>
<p><img style="width: 502px;" class="fr-fic fr-dib lazyload" data-image="true" data-new="false" data-sizeformatted="34.9 kB" data-mimetype="image/png" data-creationdate="1729538903897" data-creationdateformatted="10/21/2024 07:28 PM" data-type="temp" data-url="https://dz2cdn1.dzone.com/storage/temp/17994512-4.png" data-modificationdate="null" data-size="34924" data-name="4.png" data-id="17994512" data-src="https://dz2cdn1.dzone.com/storage/temp/17994512-4.png" alt="TLS v1.2: Protected HTTP Request"></p>
<h3 dir="ltr">Protected HTTP Request: Explanation of Steps</h3>
<ol>
<li dir="ltr"><code><strong>TCP SYN</strong></code>: The client initiates a TCP connection by sending a SYN packet to the server.</li>
<li dir="ltr"><code><strong>TCP SYN-ACK</strong></code>: The server responds to the client's SYN with a SYN-ACK packet to acknowledge the client's SYN and indicate readiness to establish a connection.</li>
<li dir="ltr"><code><strong>TCP ACK</strong></code>: The client acknowledges the server's SYN-ACK by sending an ACK packet to complete the TCP three-way handshake, establishing the TCP connection.</li>
<li dir="ltr"><code><strong>ClientHello</strong></code>: The client initiates the TLS handshake by sending a <code>ClientHello</code>message over the established TCP connection. It contains the following:
<ul>
<li dir="ltr">Protocol version: Highest TLS version supported (e.g., TLS 1.2)</li>
<li dir="ltr">Client random: A random value used in key generation</li>
<li dir="ltr">Session ID: For session resumption (may be empty)</li>
<li dir="ltr">Cipher suites: List of supported encryption algorithms</li>
<li dir="ltr">Compression methods: Usually null (no compression)</li>
<li dir="ltr">Extensions: Optional features (e.g., Server Name Indication)</li>
</ul></li>
<li dir="ltr"><code><strong>ServerHello</strong></code>: The server responds with a <code>ServerHello</code> message which contains:
<ul>
<li dir="ltr">Protocol version: Selected TLS version (must be ≤ client's version)</li>
<li dir="ltr">Server random: Another random value for key generation</li>
<li dir="ltr">Session ID: Chosen session ID (matches client's if resuming a session)</li>
<li dir="ltr">Cipher suite: Selected encryption algorithm from the client's list</li>
<li dir="ltr">Compression method: Selected compression method</li>
<li dir="ltr">Extensions: Optional features agreed upon</li>
</ul></li>
<li dir="ltr"><code><strong>Certificate</strong></code>: The server sends its X.509 Certificate which contains the server's public key and identity information to authenticate itself to the client. This could be a chain of certificates.</li>
<li dir="ltr"><code><strong>ServerKeyExchange</strong></code><strong>&nbsp;(optional)</strong>: Sent if additional key exchange parameters are needed (e.g., for <a href="https://dzone.com/articles/diffie-hellman-key-exchange-2">Diffie-Hellman key exchange</a>)</li>
<li dir="ltr"><code><strong>ServerHelloDone</strong></code>: Indicates that the server has finished sending its initial handshake messages and awaits the client's response</li>
<li dir="ltr"><code><strong>ClientKeyExchange</strong></code>: The client sends key exchange information to the server to allow both client and server to compute the shared premaster secret.</li>
<li dir="ltr"><code><strong>ChangeCipherSpec</strong></code>: The client signals that it will start using the newly negotiated encryption and keys.</li>
<li dir="ltr"><code><strong>Finished</strong></code>: The client sends a <code>Finished</code> message encrypted with the new cipher suite. It contains a hash of all previous handshake messages. It allows the server to verify that the handshake integrity is intact and that the client has the correct keys.</li>
<li dir="ltr"><code><strong>ChangeCipherSpec</strong></code>: The server signals that it will start using the negotiated encryption and keys.</li>
<li dir="ltr"><code><strong>Finished</strong></code>: The server sends a <code>Finished</code> message encrypted with the new cipher suite. It contains a hash of all previous handshake messages and allows the client to verify that the server has successfully completed the handshake and that the keys match.</li>
<li dir="ltr"><strong>HTTP Request</strong>: Now that the connection is established, the client sends the HTTP request over the encrypted TLS connection.</li>
<li dir="ltr"><strong>HTTP Response</strong>: The server processes the HTTP request and sends back the encrypted HTTP response to the client.</li>
</ol>
<p dir="ltr">A big one, isn’t it? If you analyze the diagram, one can find that it is at least 4 RTTs required to establish a connection and to get a response to the request from the client. Let’s check it in our test lab with a well-known curl-utility.</p>
<h3 dir="ltr">The Experiment</h3>
<p dir="ltr">To get various useful timings from curl, I usually create the following file somewhere in the filesystem of a server:</p>
<div class="codeMirror-wrapper" contenteditable="false">
<div contenteditable="false">
<div class="codeHeader">
<i class="icon-cancel-circled-1 cm-remove">&nbsp;</i>
</div>
<div class="codeMirror-code--wrapper" data-code="$ cat /var/tmp/curl-format.txt
\n
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; time_namelookup: &nbsp;%{time_namelookup}\n
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;time_connect: &nbsp;%{time_connect}\n
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; time_appconnect: &nbsp;%{time_appconnect}\n
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;time_pretransfer: &nbsp;%{time_pretransfer}\n
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; time_redirect: &nbsp;%{time_redirect}\n
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;time_starttransfer: &nbsp;%{time_starttransfer}\n
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ----------\n
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;time_total: &nbsp;%{time_total}\n
\n
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;size_request: &nbsp;%{size_request}\n
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; size_upload: &nbsp;%{size_upload}\n
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; size_header: &nbsp;%{size_header}\n
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; size_download: &nbsp;%{size_download}\n
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;speed_upload: &nbsp;%{speed_upload}\n
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;speed_download: &nbsp;%{speed_download}\n
\n" data-lang="">
<pre><code lang="">$ cat /var/tmp/curl-format.txt
\n
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; time_namelookup: &nbsp;%{time_namelookup}\n
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;time_connect: &nbsp;%{time_connect}\n
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; time_appconnect: &nbsp;%{time_appconnect}\n
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;time_pretransfer: &nbsp;%{time_pretransfer}\n
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; time_redirect: &nbsp;%{time_redirect}\n
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;time_starttransfer: &nbsp;%{time_starttransfer}\n
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ----------\n
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;time_total: &nbsp;%{time_total}\n
\n
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;size_request: &nbsp;%{size_request}\n
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; size_upload: &nbsp;%{size_upload}\n
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; size_header: &nbsp;%{size_header}\n
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; size_download: &nbsp;%{size_download}\n
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;speed_upload: &nbsp;%{speed_upload}\n
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;speed_download: &nbsp;%{speed_download}\n
\n</code></pre>
</div>
</div>
</div>
<p><br></p>
<p dir="ltr">Now let’s use it (<code>-w</code> parameter) and make a call using TLS 1.2 from Amsterdam to Sydney:</p>
<div class="codeMirror-wrapper" contenteditable="false">
<div contenteditable="false">
<div class="codeHeader">
<i class="icon-cancel-circled-1 cm-remove">&nbsp;</i>
</div>
<div class="codeMirror-code--wrapper" data-code="$ curl -w &quot;@/var/tmp/curl-format.txt&quot; https://server-in-sydney/ --http1.1 --tlsv1.2 --tls-max 1.2 -o /dev/null
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; time_namelookup: &nbsp;0.001645
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;time_connect: &nbsp;0.287356
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; time_appconnect: &nbsp;0.867459
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;time_pretransfer: &nbsp;0.867599
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; time_redirect: &nbsp;0.000000
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;time_starttransfer: &nbsp;1.154564
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ----------
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;time_total: &nbsp;1.154633
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;size_request: &nbsp;86
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; size_upload: &nbsp;0
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; size_header: &nbsp;181
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; size_download: &nbsp;33
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;speed_upload: &nbsp;0
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;speed_download: &nbsp;28" data-lang="">
<pre><code lang="">$ curl -w "@/var/tmp/curl-format.txt" https://server-in-sydney/ --http1.1 --tlsv1.2 --tls-max 1.2 -o /dev/null
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; time_namelookup: &nbsp;0.001645
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;time_connect: &nbsp;0.287356
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; time_appconnect: &nbsp;0.867459
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;time_pretransfer: &nbsp;0.867599
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; time_redirect: &nbsp;0.000000
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;time_starttransfer: &nbsp;1.154564
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ----------
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;time_total: &nbsp;1.154633
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;size_request: &nbsp;86
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; size_upload: &nbsp;0
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; size_header: &nbsp;181
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; size_download: &nbsp;33
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;speed_upload: &nbsp;0
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;speed_download: &nbsp;28</code></pre>
</div>
</div>
</div>
<p dir="ltr"><br></p>
<p dir="ltr">More than a second for a single request! Remember that the service is capable of serving 11.5K requests per second. Let’s try to do something with it.</p>
<h2 dir="ltr">TLSv1.3</h2>
<p dir="ltr">This is the newest stable version of the TLS protocol defined in “<a href="https://datatracker.ietf.org/doc/html/rfc8446" rel="noopener noreferrer" target="_blank">RFC 8446</a> - The Transport Layer Security (TLS) Protocol Version 1.3.” And you know what? It is great and absolutely safe to use everywhere you can set it up. The only downside is not every client supports it for now, but servers could be configured to support both TLS 1.2 and 1.3 versions, so it is not a big deal.</p>
<p>Now, let’s take a look at what is so great in TLSv1.3 for our handshake optimization. For that, here's a new sequence diagram.</p>
<p><img style="width: 500px;" class="fr-fic fr-dib lazyload" data-image="true" data-new="false" data-sizeformatted="30.8 kB" data-mimetype="image/png" data-creationdate="1729539054808" data-creationdateformatted="10/21/2024 07:30 PM" data-type="temp" data-url="https://dz2cdn1.dzone.com/storage/temp/17994517-5.png" data-modificationdate="null" data-size="30780" data-name="5.png" data-id="17994517" data-src="https://dz2cdn1.dzone.com/storage/temp/17994517-5.png" alt="TLS v1.3 Protected HTTP Request"></p>
<h3 dir="ltr">Protected HTTP Request: Explanation of Steps</h3>
<ol>
<li dir="ltr"><code><strong>TCP SYN</strong></code>: The client initiates a TCP connection by sending a SYN packet to the server.</li>
<li dir="ltr"><code><strong>TCP SYN-ACK</strong></code>: The server responds to the client's SYN with a SYN-ACK packet to acknowledge the client's SYN and indicate readiness to establish a connection.</li>
<li dir="ltr"><code><strong>TCP ACK</strong></code>: The client acknowledges the server's SYN-ACK by sending an ACK packet to complete the TCP three-way handshake, establishing the TCP connection.</li>
<li dir="ltr"><code><strong>ClientHello</strong></code>: The client initiates the TLS handshake by sending a <code>ClientHello</code>message. It proposes security parameters and provides key material for key exchange. The message contains:
<ul>
<li dir="ltr">Protocol version: Indicates TLS 1.3</li>
<li dir="ltr">Random value: A random number for key generation</li>
<li dir="ltr">Cipher suites: List of supported cipher suites</li>
<li dir="ltr">Key share extension: Contains the client's ephemeral public key for key exchange (e.g., ECDHE)</li>
<li dir="ltr">Supported versions extension: Indicates support for TLS 1.3</li>
<li dir="ltr">Other extensions: May include Server Name Indication (SNI), signature algorithms, etc.</li>
<li dir="ltr">Function: Proposes security parameters and provides key material for key exchange</li>
</ul></li>
<li dir="ltr"><code><strong>ServerHello</strong></code>: The server responds with a <code>ServerHello</code>message to agree on security parameters and complete the key exchange. Its contents include:
<ul>
<li dir="ltr">Protocol version: Confirms TLS 1.3</li>
<li dir="ltr">Random value: Another random number for key generation</li>
<li dir="ltr">Cipher suite: Selected cipher suite from the client's list</li>
<li dir="ltr">Key share extension: Contains the server's ephemeral public key</li>
<li dir="ltr">Supported versions extension: Confirms use of TLS 1.3.</li>
</ul></li>
<li dir="ltr"><code><strong>EncryptedExtensions</strong></code>: The server sends <code>EncryptedExtensions</code> containing extensions that require confidentiality, like server parameters.</li>
<li dir="ltr"><code><strong>Certificate</strong></code>: The server provides its certificate and any intermediate certificates to authenticate itself to the client.</li>
<li dir="ltr"><code><strong>CertificateVerify</strong></code>: The server proves possession of the private key corresponding to its certificate by signing all the previous handshake messages.</li>
<li dir="ltr"><code><strong>Finished</strong></code>: The server signals the completion of its handshake messages with an HMAC over the handshake messages, ensuring integrity.</li>
<li dir="ltr"><code><strong>Finished</strong></code>: The client responds with its own <code>Finished</code> message with an HMAC over the handshake messages, using keys derived from the shared secret.</li>
<li dir="ltr"><strong>HTTP Request</strong>: Now that the connection is established, the client sends the HTTP request over the encrypted TLS connection.</li>
<li dir="ltr"><strong>HTTP Response</strong>: The server processes the HTTP request and sends back the encrypted HTTP response to the client.</li>
</ol>
<p dir="ltr">As you can see, TLSv1.3 reduces the handshake to one round trip (1-RTT) compared with the previous version of the protocol. It also allows only ephemeral key exchanges and simplified cipher suites. While it is very good for security, that is not our main concern here. For us, it means less data is required to be sent between the parties.</p>
<h3 dir="ltr">The Experiment</h3>
<p dir="ltr">Let’s try it out with our curl command:</p>
<div class="codeMirror-wrapper" contenteditable="false">
<div contenteditable="false">
<div class="codeHeader">
<i class="icon-cancel-circled-1 cm-remove">&nbsp;</i>
</div>
<div class="codeMirror-code--wrapper" data-code="$ curl -w &quot;@/var/tmp/curl-format.txt&quot; https://server-in-sydney/ --http1.1 --tlsv1.3 &nbsp;-o /dev/null
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; time_namelookup: &nbsp;0.003245
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;time_connect: &nbsp;0.265230
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; time_appconnect: &nbsp;0.533588
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;time_pretransfer: &nbsp;0.533673
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; time_redirect: &nbsp;0.000000
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;time_starttransfer: &nbsp;0.795738
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ----------
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;time_total: &nbsp;0.795832
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;size_request: &nbsp;86
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; size_upload: &nbsp;0
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; size_header: &nbsp;181
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; size_download: &nbsp;33
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;speed_upload: &nbsp;0
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;speed_download: &nbsp;41" data-lang="">
<pre><code lang="">$ curl -w "@/var/tmp/curl-format.txt" https://server-in-sydney/ --http1.1 --tlsv1.3 &nbsp;-o /dev/null
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; time_namelookup: &nbsp;0.003245
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;time_connect: &nbsp;0.265230
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; time_appconnect: &nbsp;0.533588
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;time_pretransfer: &nbsp;0.533673
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; time_redirect: &nbsp;0.000000
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;time_starttransfer: &nbsp;0.795738
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ----------
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;time_total: &nbsp;0.795832
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;size_request: &nbsp;86
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; size_upload: &nbsp;0
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; size_header: &nbsp;181
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; size_download: &nbsp;33
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;speed_upload: &nbsp;0
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;speed_download: &nbsp;41</code></pre>
</div>
</div>
</div>
<p><br></p>
<p dir="ltr">And it is all true: we cut one RTT from the response. Great!</p>
<h3 dir="ltr">Mixing the Things</h3>
<p dir="ltr">We already cut one RTT by just upgrading the TLS protocol version to 1.3. Let’s remember we have TCP Fast Open available. With that, we can send a <code>ClientHello</code> message directly inside a SYN packet. Will it work? Let’s find out. Curl supports an option to enable TCP Fast Open towards the target. The target still has to support TFO.</p>
<div class="codeMirror-wrapper" contenteditable="false">
<div contenteditable="false">
<div class="codeHeader">
<i class="icon-cancel-circled-1 cm-remove">&nbsp;</i>
</div>
<div class="codeMirror-code--wrapper" data-code="$ curl -w &quot;@/var/tmp/curl-format.txt&quot; https://server-in-sydney/ --http1.1 --tlsv1.3 &nbsp;-o /dev/null --tcp-fastopen
&nbsp; % Total &nbsp; &nbsp;% Received % Xferd &nbsp;Average Speed &nbsp; Time &nbsp; &nbsp;Time &nbsp; &nbsp; Time &nbsp;Current
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;Dload &nbsp;Upload &nbsp; Total &nbsp; Spent &nbsp; &nbsp;Left &nbsp;Speed
100 &nbsp; &nbsp;33 &nbsp; &nbsp;0 &nbsp; &nbsp;33 &nbsp; &nbsp;0 &nbsp; &nbsp; 0 &nbsp; &nbsp; 57 &nbsp; &nbsp; &nbsp;0 --:--:-- --:--:-- --:--:-- &nbsp; &nbsp;57
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; time_namelookup: &nbsp;0.002215
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;time_connect: &nbsp;0.002272
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; time_appconnect: &nbsp;0.292444
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;time_pretransfer: &nbsp;0.292519
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; time_redirect: &nbsp;0.000000
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;time_starttransfer: &nbsp;0.574951
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ----------
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;time_total: &nbsp;0.575200
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;size_request: &nbsp;86
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; size_upload: &nbsp;0
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; size_header: &nbsp;181
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; size_download: &nbsp;33
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;speed_upload: &nbsp;0
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;speed_download: &nbsp;57" data-lang="">
<pre><code lang="">$ curl -w "@/var/tmp/curl-format.txt" https://server-in-sydney/ --http1.1 --tlsv1.3 &nbsp;-o /dev/null --tcp-fastopen
&nbsp; % Total &nbsp; &nbsp;% Received % Xferd &nbsp;Average Speed &nbsp; Time &nbsp; &nbsp;Time &nbsp; &nbsp; Time &nbsp;Current
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;Dload &nbsp;Upload &nbsp; Total &nbsp; Spent &nbsp; &nbsp;Left &nbsp;Speed
100 &nbsp; &nbsp;33 &nbsp; &nbsp;0 &nbsp; &nbsp;33 &nbsp; &nbsp;0 &nbsp; &nbsp; 0 &nbsp; &nbsp; 57 &nbsp; &nbsp; &nbsp;0 --:--:-- --:--:-- --:--:-- &nbsp; &nbsp;57
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; time_namelookup: &nbsp;0.002215
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;time_connect: &nbsp;0.002272
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; time_appconnect: &nbsp;0.292444
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;time_pretransfer: &nbsp;0.292519
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; time_redirect: &nbsp;0.000000
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;time_starttransfer: &nbsp;0.574951
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ----------
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;time_total: &nbsp;0.575200
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;size_request: &nbsp;86
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; size_upload: &nbsp;0
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; size_header: &nbsp;181
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; size_download: &nbsp;33
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;speed_upload: &nbsp;0
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;speed_download: &nbsp;57</code></pre>
</div>
</div>
</div>
<p dir="ltr"><br></p>
<p dir="ltr">And it does! We now halved the original TLSv1.2 timings. But let’s look at one more thing to consider.</p>
<h2 dir="ltr">Zero Round-Trip Time (0-RTT)</h2>
<p dir="ltr">TLS 1.3 doesn’t only significantly streamline the handshake process, enhancing both security and performance. It also provides a new feature called Zero Round-Trip Time (or 0-RTT). 0-RTT allows a client to start transmitting data to a server immediately, without waiting for the full TLS handshake to complete. For that, a previous TLS connection had to exist and its keys are being reused. Let’s take a look at the sequence diagram:</p>
<p dir="ltr"><img alt="sequenceDiagram
box Zero Round-Trip Time Request participant c as Client participant s as Server end
autonumber
c->>s: TCP SYN s->>c: TCP SYN + ACK c->>s: TCP ACK c->>s: ClientHello + Early Data (HTTP Request) s-->>s: Process Early Data s->>c: ServerHello s->>c: EncryptedExtensions s->>c: Finished c->>s: Finished s->>c: Application Data (HTTP Response) " title="mermaid-graph/neutral" width="620" height="699" class="fr-fic fr-dib lazyload" data-image="true" data-new="false" data-sizeformatted="58.8 kB" data-mimetype="image/png" data-creationdate="1729539099389" data-creationdateformatted="10/21/2024 07:31 PM" data-type="temp" data-url="https://dz2cdn1.dzone.com/storage/temp/17994519-1729539098238.png" data-modificationdate="null" data-size="58805" data-name="1729539098238.png" data-id="17994519" data-src="https://dz2cdn1.dzone.com/storage/temp/17994519-1729539098238.png"></p>
<h3 dir="ltr">Zero Round-Trip Time Request: Explanation of Steps</h3>
<ol>
<li dir="ltr"><code><strong>TCP SYN</strong></code>: The client initiates a TCP connection by sending a SYN packet to the server.</li>
<li dir="ltr"><code><strong>TCP SYN-ACK</strong></code>: The server responds to the client's SYN with a SYN-ACK packet to acknowledge the client's SYN and indicate readiness to establish a connection.</li>
<li dir="ltr"><code><strong>TCP ACK</strong></code>: The client acknowledges the server's SYN-ACK by sending an ACK packet to complete the TCP three-way handshake, establishing the TCP connection.</li>
<li dir="ltr"><code><strong>ClientHello</strong></code><strong>&nbsp;with early data (0-RTT data)</strong>: The client initiates the handshake and sends the HTTP request as early data (which could be an HTTP request). It contains the following:
<ul>
<li dir="ltr">Protocol version: Indicates support for TLS 1.3</li>
<li dir="ltr">Cipher suites: List of supported cipher suites</li>
<li dir="ltr">Key share extension: Contains the client's ephemeral public key for key exchange (e.g., ECDHE)</li>
<li dir="ltr">Pre-shared key (PSK): Includes a session ticket or PSK obtained from a previous connection</li>
<li dir="ltr">Early data indication: Signals the intention to send 0-RTT data</li>
<li dir="ltr">Early data (0-RTT Data): Application data (e.g., HTTP request) sent immediately, encrypted using keys derived from the PSK</li>
</ul></li>
<li dir="ltr">The <strong>server analyzes the early data</strong> and can pass it to a backend for processing.</li>
<li dir="ltr"><code><strong>ServerHello</strong></code>: The server responds with its <code>ServerHello</code>, agreeing on protocol parameters. The response contains the following:
<ul>
<li dir="ltr">Protocol version: Confirms TLS 1.3</li>
<li dir="ltr">Cipher suite: Selected cipher suite from the client's list</li>
<li dir="ltr">Key share extension: Server's ephemeral public key</li>
<li dir="ltr">Pre-shared key extension: Indicates acceptance of the PSK</li>
<li dir="ltr">Early data indication (optional): Confirms acceptance or rejection of 0-RTT data</li>
</ul></li>
<li dir="ltr"><code>EncryptedExtensions</code>: The server sends additional handshake parameters securely:
<ul>
<li dir="ltr">ALPN, supported groups, etc.</li>
<li dir="ltr">Early data indication (optional): Officially accepts or rejects the early data.</li>
</ul></li>
<li dir="ltr"><code>Finished</code>: The server signals the completion of its handshake messages with an HMAC over the handshake messages to ensure integrity.</li>
<li dir="ltr"><code>Finished</code>: The client completes the handshake with an HMAC over the handshake messages.</li>
<li dir="ltr"><strong>HTTP Response</strong>: The server responds to the request received as early data.</li>
</ol>
<p>No complete RTT saving here, but the request is being transferred as soon as possible. It gives more time to the server to process it and there are more chances that the response will be sent out right after the handshake completes. That is also a great thing.</p>
<p>Unfortunately, such an approach reduces the security as by TLS 1.3 design, session keys should not be reused at all. Also, it opens a surface for replay attacks. I’d suggest not using such a technology in a non-secure network environment unless you carefully implement security measures to compensate for those risks.</p>
<h3 dir="ltr">The Experiment That Didn’t Happen</h3>
<p dir="ltr">I wasn’t able to make an experiment as my service is a very fast responding one and doesn’t benefit from 0-RTT. But if you want to test it yourself, mind that at the moment of preparing this article, curl didn’t support early data. But you can do the testing with “<code>openssl s_client</code>” utility. This is how it can be done:</p>
<ol>
<li dir="ltr">
<p dir="ltr">Save TLS-session to the disk with the following command:</p>
<div class="codeMirror-wrapper" contenteditable="false">
<div contenteditable="false">
<div class="codeHeader">
<i class="icon-cancel-circled-1 cm-remove">&nbsp;</i>
</div>
<div class="codeMirror-code--wrapper" data-code="openssl s_client -connect example.com:443 -tls1_3 -sess_out /tmp/session.pem < /dev/null" data-lang="">
<pre><code lang="">openssl s_client -connect example.com:443 -tls1_3 -sess_out /tmp/session.pem &lt; /dev/null</code></pre>
</div>
</div>
</div><br></li>
<li dir="ltr">
<p dir="ltr">Create a file with the request:</p>
<div class="codeMirror-wrapper" contenteditable="false">
<div contenteditable="false">
<div class="codeHeader">
<i class="icon-cancel-circled-1 cm-remove">&nbsp;</i>
</div>
<div class="codeMirror-code--wrapper" data-code="echo -e &quot;GET / HTTP/1.1\r\nHost: example.com\r\n\r\n&quot; > /tmp/request.txt" data-lang="">
<pre><code lang="">echo -e "GET / HTTP/1.1\r\nHost: example.com\r\n\r\n" &gt; /tmp/request.txt</code></pre>
</div>
</div>
</div><br></li>
<li dir="ltr">
<p dir="ltr">Use the saved session to query the server:</p>
<div class="codeMirror-wrapper newest" contenteditable="false">
<div contenteditable="false">
<div class="codeHeader">
<i class="icon-cancel-circled-1 cm-remove">&nbsp;</i>
</div>
<div class="codeMirror-code--wrapper" data-code="openssl s_client -connect example.com:443 -tls1_3 -sess_in /tmp/session.pem -early_data /tmp/request.txt -trace" data-lang="">
<pre><code lang="">openssl s_client -connect example.com:443 -tls1_3 -sess_in /tmp/session.pem -early_data /tmp/request.txt -trace</code></pre>
</div>
</div>
</div><br></li>
</ol>
<h2 dir="ltr">HTTP/3</h2>
<p dir="ltr">And the last thing I wanted to talk about is HTTP/3. First, it doesn’t use TCP and thus has no requirements for TCP handshake to happen. Second, it supports the same early data approach we’ve just seen in 0-RTT. Lastly, all the congestion and retransmission control is now outside of your kernel and clearly depends on how the server’s and the client’s developers built it.</p>
<p dir="ltr">It means that for the story of handshake latency, what you get are still 2 RTTs as TLSv1.3 + TFO provided, but with that, a new high-level protocol encapsulated in UDP. But give it a try; maybe it can help.</p>
<p dir="ltr">I will give a brief sequence diagram for HTTP/3, but without deep details, as they are very close to what we’ve seen for TLS1.3 and 0-RTT:</p>
<p dir="ltr"><img alt="sequenceDiagram
box HTTP/3 Request participant c as Client participant s as Server end
autonumber
c->>s: Initial Packet (ClientHello, 0-RTT HTTP Request) s-->>s: [Process ClientHello &amp; Buffer 0-RTT HTTP Request] s->>c: Initial Packet (ServerHello) s->>c: Handshake Packets (EncryptedExtensions, Certificate, CertificateVerify, Finished) c->>s: Handshake Packet (Finished) s->>s: [Process 0-RTT HTTP Request] s->>c: HTTP Response " title="mermaid-graph/neutral" width="1016" height="591" class="fr-fic fr-dib lazyload" data-image="true" data-new="false" data-sizeformatted="76.4 kB" data-mimetype="image/png" data-creationdate="1729539124021" data-creationdateformatted="10/21/2024 07:32 PM" data-type="temp" data-url="https://dz2cdn1.dzone.com/storage/temp/17994520-1729539122857.png" data-modificationdate="null" data-size="76374" data-name="1729539122857.png" data-id="17994520" data-src="https://dz2cdn1.dzone.com/storage/temp/17994520-1729539122857.png"></p>
<h2 dir="ltr">Conclusion</h2>
<p dir="ltr">We took a dive into four available techniques intended to decrease the time spent on handshaking between a client and a server. The handshakes appear at TCP and TLS levels. The common idea for them is to send the data to the server as early as possible. Let’s recap:</p>
<ul>
<li dir="ltr"><strong>TCP FastOpen</strong> allows saving of one RTT putting the request into the SYN-packet. It is not very safe outside as it is prone to replay attacks, but stable and good to use in protected network environments. Be cautious for non-idempotent services: better to combine with TLS.</li>
<li dir="ltr"><strong>TLSv1.3</strong> is a newer version of the TLS protocol. It saves one RTT by reducing the amount of information exchanged during the handshake, is very safe, and great to be used inside and outside of trusted networks. It could be combined with TFO to save two RTTs instead of one.</li>
<li dir="ltr"><strong>Zero Round-Trip Time (0-RTT)</strong> is an extension for TLSv1.3. It allows to send a client’s request very soon; thus, giving the server more time to process it. It comes with some security concerns but is mostly safe to use inside of a trusted network perimeter for idempotent services.</li>
<li dir="ltr"><strong>HTTP/3</strong> is the newest version of the HTTP protocol. It uses UDP and thus has no need for a TCP handshake. It can give you a response after two RTTs, so similar to TLSv1.3 + TFO.</li>
</ul>
<p dir="ltr">I hope this article will be useful to make your service slightly faster. Thanks for reading!</p></div>
</div>
<div id="bottom-bumper-container"></div>
<div class="article-tag-pill-container">
<span class="article-tag-pill">Transmission Control Protocol</span>
<span class="article-tag-pill">TLS</span>
<span class="article-tag-pill">Connection (dance)</span>
<span class="article-tag-pill">Data (computing)</span>
<span class="article-tag-pill">Network</span>
<span class="article-tag-pill">Requests</span>
</div>
<div class="attribution">
<p>Opinions expressed by DZone contributors are their own.</p>
</div>
</div>
</article>
</div>
<div class="trending-separator goto"></div>
<aside class="trending goto" aria-labelledby="related-mobile-heading">
<h2 id="related-mobile-heading">Related</h2>
<ul>
<li class="item">
<a href="/articles/why-your-stateless-services-are-lying-to-you" class="goto-link related-link">Why Your "Stateless" Services Are Lying to You</a>
</li>
<li class="item">
<a href="/articles/transit-gateway" class="goto-link related-link">Transit Gateway With Anypoint Platform</a>
</li>
<li class="item">
<a href="/articles/real-time-stock-data-updates-with-websockets-using" class="goto-link related-link">Real-Time Stock Data Updates with WebSockets using Ballerina</a>
</li>
<li class="item">
<a href="/articles/load-balancing-minecraft-servers-with-kong-gateway" class="goto-link related-link">Load-Balancing Minecraft Servers with Kong Gateway</a>
</li>
</ul>
</aside>
</div>
<div id="div-gpt-ad-1435246566686-11" class="ads-bottom-leaderboard dz2_article_bottom" data-gpt-slot="bottom"></div>
<div class="layout-card widget-top-border partner-resources-block" style="width:100%; margin-bottom: 1em;">
<div class="main-container">
<div class="featured-header">
<h2>
<span>Partner Resources</span>
</h2>
</div>
<div class="partner-resources-container">
<div id="div-gpt-ad-1435246566686-5" class="resource-block dz2_partner_resource_link" data-gpt-slot="partner" data-gpt-position="pr1"></div>
<div id="div-gpt-ad-1435246566686-6" class="resource-block dz2_partner_resource_link" data-gpt-slot="partner" data-gpt-position="pr2"></div>
<div id="div-gpt-ad-1435246566686-7" class="resource-block dz2_partner_resource_link" data-gpt-slot="partner" data-gpt-position="pr3"></div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="bottom-sticky-ad-container">
<span class="bottom-sticky-ad-close" title="close"
x-on:click="$el.parentElement.classList.add('closed')">×</span>
<div id="div-gpt-ad-1635294790718-12" class="bottom-sticky-ad dz2_sticky_footer_leaderboard" data-gpt-slot="bottomStickyFooter"></div>
</div>
<div class="modal fade bd-example-modal-lg" id="modal-message" tabindex="-1" role="dialog" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header"></div>
<div class="modal-body"></div>
</div>
</div>
</div>
</div>
</div>
<div class="comments-overlay"></div>
<div id="comment-box">
<div class="comment-box-wrapper">
<div id="comment-input-editor"></div>
</div>
<div class="info hidden"></div>
<div class="comments-content" style="display: none;">
<div class="comment-header">
<hr />
<span class="icon-comment">
<span class="numOfComments"></span> Comments
</span>
</div>
<div class="comments"></div>
</div>
</div>
<script async>var articleTitle = 'Accelerating Connection Handshakes in Trusted Network Environments';
var articleUrl = 'https://dzone.com/articles/accelerating-connection-handshakes';
var retweetLink = document.querySelector('#tweet-link');
function retweet(event) {
event.preventDefault();
event.stopPropagation();
var twitter = 'https://twitter.com/intent/tweet';
var params = '?text=' + encodeURIComponent(articleTitle) + '&url=' + articleUrl + '&ref=dzone.com&via=DZoneInc';
var win = window.open(twitter + params, '_blank');
win.focus();
}
retweetLink.addEventListener('click', retweet);
function showStatusMessage(options) {
var modal = document.getElementById('modal-message');
if(modal) {
modal.classList.add(options.type);
var modalBody = modal.querySelector('.modal-content .modal-body');
var modalHeader = modal.querySelector('.modal-content .modal-header');
modalHeader.innerText = options.header ? options.header : '';
modalBody.innerText = options.body ? options.body : '';
$(modal).modal('show');
$(modal).on('hidden.bs.modal', function () {
modal.classList.remove(options.type);
});
}
}
function showConfirmMessage(options) {
var modal = document.getElementById('modal-message');
if(modal) {
modal.classList.add(options.type);
var modalBody = modal.querySelector('.modal-content .modal-body');
var modalHeader = modal.querySelector('.modal-content .modal-header');
modalHeader.innerText = options.header ? options.header : '';
modalBody.innerText = options.body ? options.body : '';
if (options.textarea) {
var textareaDiv = document.createElement('div');
var textareaLabel = document.createElement('label');
textareaLabel.setAttribute('for', 'modal-textarea');
textareaLabel.innerText = options.textarea.label;
var textarea = document.createElement('textarea');
textarea.id = 'modal-textarea';
textarea.placeholder = (options.textarea.placeholder || '');
textarea.setAttribute('rows', (options.textarea.rows || 3));
textarea.classList.add('form-control', 'not-resizable');
if (options.textarea.maxlength) {
textarea.maxLength = options.textarea.maxlength;
}
textareaDiv.appendChild(textareaLabel);
textareaDiv.appendChild(textarea);
modalBody.appendChild(textareaDiv);
}
var btnContainer = document.createElement('div');
btnContainer.classList.add('btn-container');
var noBtn = document.createElement('button');
noBtn.innerText = options.noBtnText ? options.noBtnText : 'No';
noBtn.classList.add('no-btn');
var yesBtn = document.createElement('button');
yesBtn.innerText = options.yesBtnText ? options.yesBtnText : 'Yes';
yesBtn.classList.add('yes-btn');
btnContainer.appendChild(noBtn);
btnContainer.appendChild(yesBtn);
modalBody.appendChild(btnContainer);
$(modal).modal('show');
$(noBtn).one('click', function() {
$(modal).modal('hide');
if(options.noCallback) {
$(modal).one('hidden.bs.modal', function () {
options.noCallback();
});
}
});
$(yesBtn).one('click', function() {
$(modal).modal('hide');
if(options.yesCallback) {
$(modal).one('hidden.bs.modal', function () {
options.yesCallback();
});
}
});
$(modal).on('hidden.bs.modal', function () {
modal.classList.remove(options.type);
});
}
}
function getSuspensionBody() {
return 'We regret to inform you that your DZone account has been suspended for violating our Guidelines. ' +
'Please reach out to editors@dzone.com if you would like additional information.';
}</script>
<style>.engagement-overlay {
width: 100%;
height: 100%;
position: fixed;
top: 0;
left: 0;
background: rgb(0, 0, 0, 0.5);
z-index: 10000;
}
.engagement-modal {
display: flex;
align-items: center;
justify-content: center;
position: fixed;
z-index: 10;
width: 100%;
height: 100%;
}
.engagement-modal .inner {
background-color: white;
border-radius: 0.5em;
margin: auto;
border: 1px solid #cccccc;
}
.engagement-modal .header {
padding: 20px 50px 10px 30px;
position: relative;
}
.engagement-modal .title {
font-size: 20px;
font-weight: bold;
color: #333333;
}
.engagement-modal .title:hover {
text-decoration: none;
}
.engagement-modal .close {
font-size: 18px;
position: absolute;
top: 18px;
right: 20px;
width: 32px;
height: 32px;
background: none;
border: none;
opacity: 0.8;
font-weight: bold;
text-shadow: 0 1px 0 #fff;
}
.engagement-modal .close:hover {
background-color: rgba(146, 148, 151, 0.2);
color: #000;
border-radius: 50%;
}
.engagement-modal .close:after {
content: '\2715';
}
.engagement-modal .content {
padding: 0 30px 20px;
width: min(450px, 90vw);
height: min(450px, 90vw);
scrollbar-width: thin;
overflow: auto;
overscroll-behavior: contain;
}
.engagement-modal .loading-screen,
.engagement-modal .content .center-screen {
display: flex;
justify-content: center;
align-items: center;
height: 100%;
font-size: 18px;
}
.engagement-modal .content .center-screen a {
color: var(--acc-ffffff-blue-1);
font-weight: bold;
}
.engagement-modal .loading-screen i {
font-size: 24px;
}
.engagement-modal .media-left,
.engagement-modal .media-right {
display: table-cell;
vertical-align: top;
}
.engagement-modal .media-left {
padding-right: 10px;
}
.engagement-modal .media-right {
padding-left: 10px;
}
.engagement-modal .media a {
color: black;
font-size: 16px;
}
.engagement-modal .avatar {
border-radius: 50%;
}
.engagement-modal .footer {
margin: 20px 0 0;
display: flex;
justify-content: center;
align-items: center;
background-color: unset !important;
border-top: none !important;
}
.engagement-modal .footer .btn {
border: 1px solid transparent;
}
.engagement-modal .footer .btn-primary {
color: #fff;
border-color: #2e6da4;
}
.engagement-modal .footer button {
background-color: var(--acc-ffffff-blue-2);
padding: 6px 12px;
border-radius: 4px;
}
.engagement-modal .footer button:hover {
filter: brightness(1.2);
}
@keyframes anim-spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(359deg);
}
}
.engagement-modal .anim-spin {
display: inline-block;
animation: anim-spin 2s infinite linear;
}</style>
<script>document.addEventListener('alpine:init', () => {
Alpine.data('engagementModal', (nodeId) => ({
nodeId: null,
init() {
this.nodeId = nodeId;
},
open() {
this.updateStore();
this.$store.article.engagement.open = true;
if (this.$store.article.engagement.page === 1 && !this.$store.article.engagement.unauthorized) {
this.$store.article.engagement.initializing = true;
this.fetchEngagements();
}
},
close() {
if (this.$store.article.engagement.open) {
this.$store.article.engagement.open = false;
}
},
updateStore() {
if (this.nodeId !== this.$store.article.id) {
this.$store.article.engagement.reset();
this.$store.article.id = this.nodeId;
}
},
fetchEngagements() {
if (this.$store.article.engagement.loading || !this.$store.article.engagement.additional) {
return;
}
this.$store.article.engagement.loading = true;
this.$store.global.getFromService('/services/nodes/' + this.$store.article.id + '/engagements?page=' + this.$store.article.engagement.page)
.then(res => {
res.json().then(data => {
for (let user of data.engagements) {
this.$store.article.engagement.users.push(user);
}
this.$store.article.engagement.additional = data.additional;
this.$store.article.engagement.page++;
});
})
.catch(err => {
err.json().then(data => {
if (data.error?.code === 4011) {
this.$store.article.engagement.unauthorized = true;
} else {
this.$store.modal_engagement_error.open();
}
});
})
.finally(() => {
// Allow the loader to gracefully exit
setTimeout(() => {
this.$store.article.engagement.loading = false;
this.$store.article.engagement.initializing = false;
}, 200);
});
},
formatJobData(user) {
if (user.profile.jobTitle && user.profile.companyName) {
return user.profile.jobTitle + ', ' + user.profile.companyName;
}
if (user.profile.jobTitle) {
return user.profile.jobTitle;
}
if (user.profile.companyName) {
return user.profile.companyName;
}
return null;
},
isDataUnavailable() {
return !this.$store.article.engagement.initializing && !this.$store.article.engagement.users.length;
},
isFooterShowing() {
return !this.$store.article.engagement.initializing
&& this.$store.article.engagement.additional
&& this.$store.article.engagement.page > 1;
}
}));
});
</script>
<div class="alpine-overlay" x-cloak x-show="$store.modal_engagement_error._open">
<div id="modal_engagement_error"
class="alpine-modal"
role="dialog"
tabindex="-1"
x-show="$store.modal_engagement_error._open"
x-on:click.self="$store.modal_engagement_error.dismissible && $store.modal_engagement_error.close()"
x-cloak
x-transition
>
<div class="dz-style modal-inner centered relative">
<button x-show="$store.modal_engagement_error.closeable"
class="btn-close"
x-on:click="$store.modal_engagement_error.close()"
aria-label="Close">
</button>
<div x-show="$store.modal_engagement_error.title" class="modal-header p-sm mt-sm mb-md">
<div class="title " x-text="$store.modal_engagement_error.title"></div>
</div>
<div class="modal-content py-md px-lg" x-bind:class="{ 'has-top-border': $store.modal_engagement_error.title }">
<p x-show="$store.modal_engagement_error.bodyText" class="my-none" x-text="$store.modal_engagement_error.bodyText"></p>
<p>The likes didn't load as expected. Please refresh the page and try again.</p>
</div>
<div x-show="$store.modal_engagement_error.options.length" class="modal-footer p-sm">
<template x-for="option in $store.modal_engagement_error.options">
<button x-show="option.canDisplay" x-text="option.text"
type="button"
x-bind:id="'button_modal_engagement_error_' + option.index"
x-bind:class="option.classList"
x-on:click="option.onClick"
x-bind:disabled="option.isDisabled"
></button>
</template>
</div>
</div>
</div>
</div>
<script>
document.addEventListener("alpine:init", () => {
Alpine.store("modal_engagement_error", {
_open: false,
title: "Oops! Something Went Wrong",
bodyText: null,
options: [{"action":"dismiss","text":"Close"}],
closeable: true,
dismissible: true,
open(data) {
if (data) {
if (data.title) {
this.title = data.title;
}
if (data.bodyText) {
this.bodyText = data.bodyText;
}
if (data.options) {
this.options = data.options;
}
if (data.dismissAfterMs) {
setTimeout(() => this._open = false, data.dismissAfterMs);
}
}
this.updateOptions();
this._open = true;
},
close() {
// Manually click the buttons in which should run on dismiss.
// This is to keep the context. Calling this[option.action]() will not work.
for (let option of this.options) {
if (option.runOnDismiss) {
const element = document.querySelector('#button_modal_engagement_error_' + option.index);
element.click();
}
}
this._open = false;
},
updateOptions() {
const btnStyle = this.options.length === 2 ? ' btn-default' : '';
this.options = this.options.map((o, idx) => ({
...o,
index: idx,
classList: o.classList ? o.classList : (idx === 0 ? "btn btn-primary hov-brighten" : "btn hov-underline" + btnStyle),
onClick: function() {
if (o.action !== "dismiss") {
this[o.action]();
}
this.$store["modal_engagement_error"].close();
},
isDisabled: function() {
return Object.hasOwn(o, 'disabled') && this[o.disabled]();
},
canDisplay: function() {
return !Object.hasOwn(o, 'condition') || this[o.condition]();
}
}));
},
init() {
this.updateOptions();
}
});
});
</script>
<link rel="stylesheet" media="all" href="https://dz2cdn1.dzone.com/themes/dz20/ftl/footer/styles.css">
<div id="ftl-footer">
<div class="container-fluid footerOuter" style="padding-bottom: 90px;">
<div class="row">
<div class="col-md-12">
<div class="container">
<div class="row footer">
<div class="col-md-12 footerWidget">
<div class="row footerContainer footer">
<div class="left col-xs-12 col-sm-7">
<div class="col-xs-12 social-media-icons footer-mobile">
<ul class="icons-only">
<li class="rss-icon" id="rss-footer-1">
<a href="/pages/feeds" target="_blank" rel="noreferrer noopener">
<svg role="img" viewBox="0 0 24 24" class="w-4 h-4 mt-1.75 fill-current" aria-label="Follow our RSS feeds">
<title>RSS</title>
<path d="M19.199 24C19.199 13.467 10.533 4.8 0 4.8V0c13.165 0 24 10.835 24 24h-4.801zM3.291 17.415c1.814 0 3.293 1.479 3.293 3.295 0 1.813-1.485 3.29-3.301 3.29C1.47 24 0 22.526 0 20.71s1.475-3.294 3.291-3.295zM15.909 24h-4.665c0-6.169-5.075-11.245-11.244-11.245V8.09c8.727 0 15.909 7.184 15.909 15.91z"></path>
</svg>
</a>
</li>
<li class="twitter-icon">
<a href="https://twitter.com/DZoneInc" target="_blank" rel="noreferrer noopener">
<svg role="img" viewBox="0 0 24 24" aria-label="Follow us on X">
<title>X</title>
<path d="M14.234 10.162 22.977 0h-2.072l-7.591 8.824L7.251 0H.258l9.168 13.343L.258 24H2.33l8.016-9.318L16.749 24h6.993zm-2.837 3.299-.929-1.329L3.076 1.56h3.182l5.965 8.532.929 1.329 7.754 11.09h-3.182z"></path>
</svg>
</a>
</li>
<li class="facebook-icon">
<a href="https://www.facebook.com/DZoneInc" target="_blank" rel="noreferrer noopener">
<svg role="img" viewBox="0 0 24 24" aria-label="Follow us on Facebook">
<title>Facebook</title>
<path d="M9.101 23.691v-7.98H6.627v-3.667h2.474v-1.58c0-4.085 1.848-5.978 5.858-5.978.401 0 .955.042 1.468.103a8.68 8.68 0 0 1 1.141.195v3.325a8.623 8.623 0 0 0-.653-.036 26.805 26.805 0 0 0-.733-.009c-.707 0-1.259.096-1.675.309a1.686 1.686 0 0 0-.679.622c-.258.42-.374.995-.374 1.752v1.297h3.919l-.386 2.103-.287 1.564h-3.246v8.245C19.396 23.238 24 18.179 24 12.044c0-6.627-5.373-12-12-12s-12 5.373-12 12c0 5.628 3.874 10.35 9.101 11.647Z"></path>
</svg>
</a>
</li>
<li class="linkedin-icon">
<a href="https://www.linkedin.com/company/dzone/" target="_blank"
rel="noreferrer noopener">
<svg viewBox="0 0 24 25" fill="none" aria-label="Follow us on LinkedIn">
<path d="M6.20062 21.2143H1.84688V7.194H6.20062V21.2143ZM4.02141 5.2815C2.62922 5.2815 1.5 4.12838 1.5 2.73619C1.5 2.06747 1.76565 1.42614 2.2385 0.953285C2.71136 0.48043 3.35269 0.214783 4.02141 0.214783C4.69012 0.214783 5.33145 0.48043 5.80431 0.953285C6.27716 1.42614 6.54281 2.06747 6.54281 2.73619C6.54281 4.12838 5.413 5.2815 4.02141 5.2815ZM22.4953 21.2143H18.1509V14.3893C18.1509 12.7628 18.1181 10.6768 15.8873 10.6768C13.6237 10.6768 13.2769 12.444 13.2769 14.2721V21.2143H8.92781V7.194H13.1034V9.1065H13.1644C13.7456 8.00494 15.1655 6.84244 17.2838 6.84244C21.69 6.84244 22.5 9.744 22.5 13.5128V21.2143H22.4953Z" fill="currentColor"></path>
</svg>
</a>
</li>
</ul>
</div>
<div class="top-section col-xs-12">
<div class="col-xs-12 col-sm-6">
<p class="section-header">ABOUT US</p>
<ul class="link-group">
<li><a href="/pages/about" rel="noreferrer noopener">About DZone</a></li>
<li><a href="/cdn-cgi/l/email-protection#d5a6a0a5a5baa7a195b1afbabbb0fbb6bab8" rel="noreferrer noopener">Support and feedback</a></li>
<li><a href="/pages/dzone-community-research">Community research</a></li>
</ul>
</div>
<div class="col-xs-12 col-sm-6">
<p class="section-header">ADVERTISE</p>
<ul class="link-group">
<li><a href="https://advertise.dzone.com" target="_blank" rel="noreferrer noopener">Advertise with DZone</a></li>
</ul>
</div>
</div>
<div class="bottom-section col-xs-12">
<div class="col-xs-12 col-sm-6">
<p class="section-header">CONTRIBUTE ON DZONE</p>
<ul class="bottom-top-list link-group">
<li><a href="/articles/dzones-article-submission-guidelines">Article Submission Guidelines</a></li>
<li><a href="/pages/contribute" rel="noreferrer noopener">Become a Contributor</a></li>
<li><a href="/pages/core" rel="noreferrer noopener">Core Program</a></li>
<li><a href="/writers-zone" rel="noreferrer noopener">Visit the Writers' Zone</a></li>
</ul>
<p class="section-header">LEGAL</p>
<ul class="link-group">
<li><a href="https://technologyadvice.com/terms-conditions/" target="_blank" rel="noreferrer noopener">Terms of Service</a></li>
<li><a href="https://technologyadvice.com/privacy-policy/" target="_blank" rel="noreferrer noopener">Privacy Policy</a></li>
</ul>
</div>
<div class="col-xs-12 col-sm-6">
<p class="section-header">CONTACT US</p>
<ul class="link-group">
<li>3343 Perimeter Hill Drive</li>
<li>Suite 215</li>
<li>Nashville, TN 37211</li>
<li><a href="/cdn-cgi/l/email-protection#45363035352a373105213f2a2b206b262a28" rel="noreferrer noopener"><span class="__cf_email__" data-cfemail="d5a6a0a5a5baa7a195b1afbabbb0fbb6bab8">[email&#160;protected]</span></a></li>
</ul>
</div>
</div>
</div>
<div class="right col-xs-12 col-sm-5">
<p class="connect-text">Let's be friends:</p>
<div class="col-xs-12 social-media-icons footer-wide">
<ul class="icons-only">
<li class="rss-icon" id="rss-footer-1">
<a href="/pages/feeds" target="_blank" rel="noreferrer noopener">
<svg role="img" viewBox="0 0 24 24" aria-label="Follow our RSS feeds">
<title>RSS</title>
<path d="M19.199 24C19.199 13.467 10.533 4.8 0 4.8V0c13.165 0 24 10.835 24 24h-4.801zM3.291 17.415c1.814 0 3.293 1.479 3.293 3.295 0 1.813-1.485 3.29-3.301 3.29C1.47 24 0 22.526 0 20.71s1.475-3.294 3.291-3.295zM15.909 24h-4.665c0-6.169-5.075-11.245-11.244-11.245V8.09c8.727 0 15.909 7.184 15.909 15.91z"></path>
</svg>
</a>
</li>
<li class="twitter-icon">
<a href="https://twitter.com/DZoneInc" target="_blank" rel="noreferrer noopener">
<svg role="img" viewBox="0 0 24 24" aria-label="Follow us on X">
<title>X</title>
<path d="M14.234 10.162 22.977 0h-2.072l-7.591 8.824L7.251 0H.258l9.168 13.343L.258 24H2.33l8.016-9.318L16.749 24h6.993zm-2.837 3.299-.929-1.329L3.076 1.56h3.182l5.965 8.532.929 1.329 7.754 11.09h-3.182z"></path>
</svg>
</a>
</li>
<li class="facebook-icon">
<a href="https://www.facebook.com/DZoneInc" target="_blank" rel="noreferrer noopener">
<svg role="img" viewBox="0 0 24 24" aria-label="Follow us on Facebook">
<title>Facebook</title>
<path d="M9.101 23.691v-7.98H6.627v-3.667h2.474v-1.58c0-4.085 1.848-5.978 5.858-5.978.401 0 .955.042 1.468.103a8.68 8.68 0 0 1 1.141.195v3.325a8.623 8.623 0 0 0-.653-.036 26.805 26.805 0 0 0-.733-.009c-.707 0-1.259.096-1.675.309a1.686 1.686 0 0 0-.679.622c-.258.42-.374.995-.374 1.752v1.297h3.919l-.386 2.103-.287 1.564h-3.246v8.245C19.396 23.238 24 18.179 24 12.044c0-6.627-5.373-12-12-12s-12 5.373-12 12c0 5.628 3.874 10.35 9.101 11.647Z"></path>
</svg>
</a>
</li>
<li class="linkedin-icon">
<a href="https://www.linkedin.com/company/dzone/" target="_blank"
rel="noreferrer noopener">
<svg viewBox="0 0 24 25" fill="none" aria-label="Follow us on LinkedIn">
<path d="M6.20062 21.2143H1.84688V7.194H6.20062V21.2143ZM4.02141 5.2815C2.62922 5.2815 1.5 4.12838 1.5 2.73619C1.5 2.06747 1.76565 1.42614 2.2385 0.953285C2.71136 0.48043 3.35269 0.214783 4.02141 0.214783C4.69012 0.214783 5.33145 0.48043 5.80431 0.953285C6.27716 1.42614 6.54281 2.06747 6.54281 2.73619C6.54281 4.12838 5.413 5.2815 4.02141 5.2815ZM22.4953 21.2143H18.1509V14.3893C18.1509 12.7628 18.1181 10.6768 15.8873 10.6768C13.6237 10.6768 13.2769 12.444 13.2769 14.2721V21.2143H8.92781V7.194H13.1034V9.1065H13.1644C13.7456 8.00494 15.1655 6.84244 17.2838 6.84244C21.69 6.84244 22.5 9.744 22.5 13.5128V21.2143H22.4953Z" fill="currentColor"></path>
</svg>
</a>
</li>
</ul>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<script data-cfasync="false" src="/cdn-cgi/scripts/5c5dd728/cloudflare-static/email-decode.min.js"></script><script>
const articleId = 3504849;
const likes = 20;
const assetDomain = 'https://dz2cdn1.dzone.com';
const codemirrorVars = {
modeURI: 'https://dz2cdn1.dzone.com/themes/dz20/lib/codemirror/mode/',
requiredScripts: [
'https://dz2cdn1.dzone.com/themes/dz20/lib/codemirror/lib/codemirror.js',
'https://dz2cdn1.dzone.com/themes/dz20/lib/codemirror/addon/mode/overlay.js',
'https://dz2cdn1.dzone.com/themes/dz20/lib/codemirror/addon/mode/multiplex.js',
'https://dz2cdn1.dzone.com/themes/dz20/lib/codemirror/mode/meta.js'
]
};
const gptTags = {
'zone': 'Performance',
'topicTag': 'Transmission Control Protocol,TLS,connection,data,network,requests',
'company': '',
'siteSection': 'Zones',
'articleCategory': 'tutorial',
'nodeID': '3504849',
'authorID': '5179298',
'publishYear': '2024',
'publishMonth': '10',
'jobRole': '',
'companySize': '',
'env': 'prod'
};
const minCommentChar = 10;
</script>
<script async>const width = window.innerWidth;
const metadata = {
'top': {
'position': 'top',
'slot': 'dz2_article_billboard_new',
},
'sponsorLogo': {
'position': 'zoneHomepage',
'slot': 'dz2_homepage_sponsor_logo',
'refreshable': false
},
'sidebar1': {
'position': 'sidebar',
'slot': 'dz2_article_halfpage_new',
'minWidthToShow': 1024
},
'topBumper': {
'position': 'top',
'slot': 'dz2_bumper_text_ad',
'minWidthToShow': 1024
},
'bottomBumper': {
'position': 'bottom',
'slot': 'dz2_bumper_text_ad',
'minWidthToShow': 1024
},
'bottom': {
'position': 'bottom',
'slot': 'dz2_article_bottom',
},
'bottomStickyFooter': {
'position': 'sticky',
'slot': 'dz2_sticky_footer_leaderboard',
},
'partner': {
'slot': 'dz2_partner_resource_link',
},
'branded': {
'slot': 'dz2_branded_content',
'refreshable': false
},
'topicBillboard': {
'position': ['top', 'zoneHomepage'],
'slot': 'dz2_topic_billboard',
},
'listPageSidebar': {
'position': 'sidebar',
'slot': 'dz2_list_page_sidebar',
'minWidthToShow': 1024
},
'topListPageLeaderboard2': {
'position': ['top', 'zoneList'],
'slot': 'dz2_list_page_leaderboard_2',
},
'bottomListPageLeaderboard2': {
'position': ['bottom', 'zoneList'],
'slot': 'dz2_list_page_leaderboard_2',
},
'homepageLeaderboard': {
'position': 'top',
'slot': 'dz2_homepage_leaderboard',
},
'homepageLeaderboard2': {
'position': 'bottom',
'slot': 'dz2_homepage_leaderboard_2',
},
'skybox': {
'position': 'above nav',
'slot': 'dz_skybox',
'refreshable': false
},
'inline': {
'position': 'inline',
'slot': 'dz2_inline-article-display',
}
};
var campaign = new URLSearchParams(window.location.search).get("adTargeting_campaign");
window.googletag = window.googletag || { cmd: [] };
if (campaign) {
window.googletag.cmd.push(function() {
window.googletag.setConfig({ targeting: { campaign }});
});
}
let lastHeader = null;
const topContainer = document.querySelector('#top-bumper-container');
const bottomContainer = document.querySelector('#bottom-bumper-container');
function GAM_getPersistentValue(key) {
const stored = JSON.parse(localStorage.getItem(key));
if (!stored) {
return null;
}
const { value, expiration } = stored;
if (expiration && Date.now() >= expiration) {
localStorage.removeItem(key);
return null;
}
return value;
}
function GAM_setPersistentValue(key, value, expiration) {
// 5 minutes from now
if (!expiration) {
expiration = Date.now() + 300000;
}
const storedValue = JSON.stringify({
value: value,
expiration: expiration
});
localStorage.setItem(key, storedValue);
return value;
}
function GAM_synchronousRequest(params) {
const xhr = new XMLHttpRequest();
xhr.open('GET', params.url, false);
xhr.setRequestHeader("Content-Type", "application/json");
if (typeof params.auth_header !== "undefined") {
xhr.setRequestHeader("Authorization", params.auth_header);
}
xhr.send(null);
if (xhr.status === 200) {
return xhr.responseText;
} else {
throw new Error('Request failed: ' + xhr.statusText);
}
}
function GAM_fetch_data(params) {
let stored_data = GAM_getPersistentValue(params.storage_key);
if (stored_data === null) {
try {
const response = GAM_synchronousRequest(params);
const data = JSON.parse(response);
// Store and expire after 30 minutes
return GAM_setPersistentValue(params.storage_key, data, Date.now() + 1800000);
} catch (error) {
console.error('Could not get ' + params.storage_key + ' data: ' + error);
return null;
}
} else {
return stored_data;
}
}
function GAM_setUpSixSenseTargeting() {
let meData = GAM_fetch_data({
url: "https://link.technologyadvice.com/_me",
storage_key: "ta_me_data"
});
let sixSenseData = GAM_fetch_data({
url: "https://epsilon.6sense.com/v3/company/details",
auth_header: "Token d20a1b0e892442270cbc4cb6801c0160d28af04c",
storage_key: "ta_6s_data"
});
function meDataIncludes(i) {
return meData.tags.includes(i);
}
if (typeof meData !== "undefined" && meData !== null) {
window.googletag.pubads().setTargeting("visitor_id", meData.vid);
window.googletag.pubads().setTargeting("user_agent", meData.user_agent);
var tags_mapping = {
"is_datacenter": ["site.is-datacenter"],
"is_suspected_bot": ["site.suspected-bad-bot", "site.bad-bot"],
"is_ta_user": ["site.is-ta-user"],
"is_crawler": ["site.user-agent-blocked"],
"is_ad_blocked": ["site.is-ad-blocked"],
};
for (var key in tags_mapping) {
if (tags_mapping[key].some(meDataIncludes)) {
window.googletag.pubads().setTargeting(key, 'true');
}
}
}
if (typeof sixSenseData !== "undefined" && sixSenseData !== null) {
var segment_ids = [];
if (sixSenseData.segments && sixSenseData.segments.ids && sixSenseData.segments.ids.length) {
sixSenseData.segments.ids.forEach(function (v) {
segment_ids.push(v.toString());
});
}
if (typeof segment_ids !== "undefined" && segment_ids.length > 0) {
window.googletag.pubads().setTargeting("segment_ids_6si", segment_ids);
}
}
}
if (width >= 1024 && (topContainer || bottomContainer)) {
// Dynamically create bumper ad slots for non-mobile devices.
// Prevents ugly dividers being rendered on empty ad slots (mobile)
const topBumper = document.createElement('div');
topBumper.id = 'div-gpt-ad-1435246566686-3';
topBumper.classList.add('article-bumper', 'article-bumper-top', 'dz2_bumper_text_ad');
topBumper.setAttribute('data-gpt-slot', 'topBumper');
topContainer.appendChild(topBumper);
const bottomBumper = document.createElement('div');
bottomBumper.id = 'div-gpt-ad-1435246566686-4';
bottomBumper.classList.add('article-bumper', 'article-bumper-bottom', 'dz2_bumper_text_ad');
bottomBumper.setAttribute('data-gpt-slot', 'bottomBumper');
bottomContainer.appendChild(bottomBumper);
}
if (gptTags.zone) {
gptTags.zone = gptTags.zone.replaceAll(/[\s/]/g, '_')
.replaceAll(/[^a-zA-Z0-9_]/g, '')
.toLowerCase();
}
makeAds();
function handleSkybox() {
const skybox = document.querySelector('div.skybox');
if (skybox) {
document.body.classList.add('skybox-auto-collapse');
// observe classlist changes for offsetting sticky ads
const observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
if (mutation.type === 'attributes' && mutation.attributeName === 'class') {
let offset = 0;
if (!document.body.classList.contains('skybox-closed') && (
document.body.classList.contains('ccad-skybox-manualcollapse')
|| document.body.classList.contains('ccad-skybox-manualexpand')
)) {
offset = skybox.getBoundingClientRect().height;
}
let eligibleAds = [
{ element: document.querySelector('.content-right-images'), offset: 108 },
{ element: document.querySelector('.trending-sidebar'), offset: 88 },
{ element: document.querySelector('.trending'), offset: 88 }
];
for (let ad of eligibleAds) {
if (ad.element) {
ad.element.style.top = (ad.offset + offset) + 'px';
}
}
}
});
});
observer.observe(document.body, { attributes: true });
}
}
function isHeaderEligible(header) {
const range = document.createRange();
range.setStartAfter(lastHeader);
range.setEndBefore(header);
return range.toString().trim().length >= 600;
}
function placeInlineAds() {
const excludedContent = document.querySelectorAll('#ftl-article.branded-content, #ftl-article.sponsored');
if (excludedContent.length > 0) {
return;
}
const headers = Array.from(document.querySelectorAll('.content-html h2')).splice(1);
let globalIndex = 12;
for (let i = 0; i < headers.length; i++) {
if (!lastHeader || isHeaderEligible(headers[i])) {
const element = document.createElement('div');
element.id = 'div-gpt-ad-1435246566686-' + globalIndex;
element.classList.add('inline-display', 'dz2_inline-article-display');
element.setAttribute('data-gpt-slot', 'inline');
headers[i].before(element);
lastHeader = element;
globalIndex++;
}
}
}
function makeAds() {
handleSkybox();
placeInlineAds();
const script = document.createElement('script');
script.src = 'https://i6ByW9Zmz4ncxhHkb.ay.delivery/manager/i6ByW9Zmz4ncxhHkb';
script.type = 'text/javascript';
script.referrerPolicy = 'no-referrer-when-downgrade';
document.head.appendChild(script);
window.googletag.cmd.push(function() {
const containers = document.querySelectorAll('div[data-gpt-slot]');
for (let container of containers) {
const div = container.getAttribute('data-gpt-slot');
const meta = metadata[div];
if (meta.minWidthToShow && width < meta.minWidthToShow) {
continue;
}
window.googletag.pubads().setTargeting('hostname', window.location.hostname);
Object.keys(gptTags).forEach(function(key) {
window.googletag.pubads().setTargeting(key, gptTags[key]);
});
window.googletag.pubads().addEventListener('slotRenderEnded', (event) => {
window.requestAnimationFrame(() => {
var slotId = event.slot.getSlotElementId();
if (!slotId.includes('__ayManagerEnv__')) {
return;
}
var className = slotId.split('__ayManagerEnv__')[0];
var slotName = className.replace('dz2_', '').replace('dz_', '').replace(/_\d+$/, '');
var unitName = meta.slot.replace('dz2_', '').replace('dz_', '').replace(/_\d+$/, '');
if (slotName !== unitName) {
return;
}
var elem = document.getElementById(slotId);
if (!elem) {
console.warn('Ad element missing', slotId);
return;
}
// Ad unit did not fill, collapse slot
if (event.isEmpty && elem.parentElement) {
elem.parentElement.style.display = 'none';
}
});
});
}
GAM_setUpSixSenseTargeting();
});
}
</script>
<script async>// InMobi Choice. Consent Manager Tag v3.0 (for TCF 2.2)
!function(){var e=window.location.hostname,t=document.createElement("script"),n=document.getElementsByTagName("script")[0],a="https://cmp.inmobi.com".concat("/choice/","vPn77x7pBG57Y","/","dzone.com","/choice.js?tag_version=V3"),p=0;t.async=!0,t.type="text/javascript",t.src=a,n.parentNode.insertBefore(t,n),function(){for(var e,t="__tcfapiLocator",n=[],a=window;a;){try{if(a.frames[t]){e=a;break}}catch(e){}if(a===window.top)break;a=a.parent}e||(!function e(){var n=a.document,p=!!a.frames[t];if(!p)if(n.body){var s=n.createElement("iframe");s.style.cssText="display:none",s.name=t,n.body.appendChild(s)}else setTimeout(e,5);return!p}(),a.__tcfapi=function(){var e,t=arguments;if(!t.length)return n;if("setGdprApplies"===t[0])t.length>3&&2===t[2]&&"boolean"==typeof t[3]&&(e=t[3],"function"==typeof t[2]&&t[2]("set",!0));else if("ping"===t[0]){var a={gdprApplies:e,cmpLoaded:!1,cmpStatus:"stub"};"function"==typeof t[2]&&t[2](a)}else"init"===t[0]&&"object"==typeof t[3]&&(t[3]=Object.assign(t[3],{tag_version:"V3"})),n.push(t)},a.addEventListener("message",(function(e){var t="string"==typeof e.data,n={};try{n=t?JSON.parse(e.data):e.data}catch(e){}var a=n.__tcfapiCall;a&&window.__tcfapi(a.command,a.version,(function(n,p){var s={__tcfapiReturn:{returnValue:n,success:p,callId:a.callId}};t&&(s=JSON.stringify(s)),e&&e.source&&e.source.postMessage&&e.source.postMessage(s,"*")}),a.parameter)}),!1))}(),function(){const e=["2:tcfeuv2","6:uspv1","7:usnatv1","8:usca","9:usvav1","10:uscov1","11:usutv1","12:usctv1"];window.__gpp_addFrame=function(e){if(!window.frames[e])if(document.body){var t=document.createElement("iframe");t.style.cssText="display:none",t.name=e,document.body.appendChild(t)}else window.setTimeout(window.__gpp_addFrame,10,e)},window.__gpp_stub=function(){var t=arguments;if(__gpp.queue=__gpp.queue||[],__gpp.events=__gpp.events||[],!t.length||1==t.length&&"queue"==t[0])return __gpp.queue;if(1==t.length&&"events"==t[0])return __gpp.events;var n=t[0],a=t.length>1?t[1]:null,p=t.length>2?t[2]:null;if("ping"===n)a({gppVersion:"1.1",cmpStatus:"stub",cmpDisplayStatus:"hidden",signalStatus:"not ready",supportedAPIs:e,cmpId:10,sectionList:[],applicableSections:[-1],gppString:"",parsedSections:{}},!0);else if("addEventListener"===n){"lastId"in __gpp||(__gpp.lastId=0),__gpp.lastId++;var s=__gpp.lastId;__gpp.events.push({id:s,callback:a,parameter:p}),a({eventName:"listenerRegistered",listenerId:s,data:!0,pingData:{gppVersion:"1.1",cmpStatus:"stub",cmpDisplayStatus:"hidden",signalStatus:"not ready",supportedAPIs:e,cmpId:10,sectionList:[],applicableSections:[-1],gppString:"",parsedSections:{}}},!0)}else if("removeEventListener"===n){for(var i=!1,o=0;o<__gpp.events.length;o++)if(__gpp.events[o].id==p){__gpp.events.splice(o,1),i=!0;break}a({eventName:"listenerRemoved",listenerId:p,data:i,pingData:{gppVersion:"1.1",cmpStatus:"stub",cmpDisplayStatus:"hidden",signalStatus:"not ready",supportedAPIs:e,cmpId:10,sectionList:[],applicableSections:[-1],gppString:"",parsedSections:{}}},!0)}else"hasSection"===n?a(!1,!0):"getSection"===n||"getField"===n?a(null,!0):__gpp.queue.push([].slice.apply(t))},window.__gpp_msghandler=function(e){var t="string"==typeof e.data;try{var n=t?JSON.parse(e.data):e.data}catch(e){n=null}if("object"==typeof n&&null!==n&&"__gppCall"in n){var a=n.__gppCall;window.__gpp(a.command,(function(n,p){var s={__gppReturn:{returnValue:n,success:p,callId:a.callId}};e.source.postMessage(t?JSON.stringify(s):s,"*")}),"parameter"in a?a.parameter:null,"version"in a?a.version:"1.1")}},"__gpp"in window&&"function"==typeof window.__gpp||(window.__gpp=window.__gpp_stub,window.addEventListener("message",window.__gpp_msghandler,!1),window.__gpp_addFrame("__gppLocator"))}();var s=function(){var e=arguments;typeof window.__uspapi!==s&&setTimeout((function(){void 0!==window.__uspapi&&window.__uspapi.apply(window.__uspapi,e)}),500)};if(void 0===window.__uspapi){window.__uspapi=s;var i=setInterval((function(){p++,window.__uspapi===s&&p<3?console.warn("USP is not accessible"):clearInterval(i)}),6e3)}}();
</script>
<script async>
(function(w, d, s, l, i) {
w[l] = w[l] || [];
w[l].push({'gtm.start': new Date().getTime(), event: 'gtm.js'});
var f = d.getElementsByTagName(s)[0], j = d.createElement(s), dl = l != 'dataLayer' ? '&l=' + l : '';
j.async = true;
j.src = 'https://www.googletagmanager.com/gtm.js?id=' + i + dl;
f.parentNode.insertBefore(j,f);
})(window, document, 'script', 'dataLayer', 'GTM-K25QL22');
</script>
<script>
window.ga=window.ga||function(){(ga.q=ga.q||[]).push(arguments)};ga.l=+new Date;
ga('create', 'UA-410289-1', 'auto');
ga('require', 'linkid', 'linkid.js');
ga('require', 'GTM-TSD9TZP');
ga('set', 'siteSpeedSampleRate', 25);
</script>
<script async src="https://www.google-analytics.com/analytics.js"></script>
<script async>var analytics = {
'dimension1': 'Performance',
'dimension2': 'article/tutorial',
'dimension3': '2024-10-24',
'dimension4': '0',
'dimension5': '',
'dimension7': 'Transmission Control Protocol, TLS, connection, data, network, requests',
'dimension8': 'Max_Kupriianov',
'dimension9': 'undefined',
'dimension10': 'Maksim Kupriianov'
};
if (window.ga) {
Object.keys(analytics).forEach(function(key) {
window.ga('set', key, analytics[key]);
});
window.ga('send', 'pageview');
}</script>
<script src="https://dz2cdn1.dzone.com/themes/dz20/lib/static/jquery/jquery.min.js"></script>
<script async src="https://dz2cdn1.dzone.com/themes/dz20/lib/static/bootstrap/bootstrap.min.js"></script>
<script>
function loadScript(src) {
return new Promise(function (resolve, reject) {
const s = document.createElement('script');
s.src = src;
s.onload = resolve;
s.onerror = reject;
document.head.appendChild(s);
});
}
function loadStyle(href) {
const link = document.createElement('link');
link.rel = 'stylesheet';
link.href = href;
document.head.appendChild(link);
}
function loadScriptsSync(deferred) {
var p = Promise.resolve();
for (var i = 0; i < deferred.length; i++) {
let script = deferred[i];
p = p.then(function() {
return loadScript(script);
});
}
return p;
}
function loadStyles() {
const deferred = [
'https://dz2cdn1.dzone.com/themes/dz20/lib/codemirror/lib/codemirror.css',
'https://dz2cdn1.dzone.com/themes/dz20/ftl/comments/styles.css',
'https://dz2cdn1.dzone.com/themes/dz20/lib/froala3/css/froala_editor.pkgd.min.css',
'https://dz2cdn1.dzone.com/themes/dz20/lib/froala3/css/themes/gray.min.css',
'https://dz2cdn1.dzone.com/themes/dz20/ftl/article/mini-profile.css'
];
for (var i = 0; i < deferred.length; i++) {
loadStyle(deferred[i]);
}
}
window.addEventListener('load', function(_event) {
loadStyles();
loadScriptsSync([
'https://dz2cdn1.dzone.com/themes/dz20/lib/lazysizes.min.js',
'https://dz2cdn1.dzone.com/themes/dz20/ftl/article/codeblocks.js',
'https://dz2cdn1.dzone.com/themes/dz20/ftl/article/activity-bar.js',
'https://dz2cdn1.dzone.com/themes/dz20/lib/froala3/js/froala_editor.pkgd.min.js',
'https://dz2cdn1.dzone.com/themes/dz20/ftl/froala/content.js',
'https://dz2cdn1.dzone.com/themes/dz20/ftl/comments/content.js',
'https://dz2cdn1.dzone.com/themes/dz20/ftl/article/content.js'
]);
});
</script>
</body>
</html>