Skip to content

Instantly share code, notes, and snippets.

@igrigorik
Last active April 29, 2023 14:54
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save igrigorik/b65e55d81e4117b415d2 to your computer and use it in GitHub Desktop.
Save igrigorik/b65e55d81e4117b415d2 to your computer and use it in GitHub Desktop.
ServiceWorker takeover with HTTP/2 server push.
-----BEGIN CERTIFICATE-----
MIID1zCCAr+gAwIBAgIJANjbVITTVqaAMA0GCSqGSIb3DQEBBQUAMFAxCzAJBgNV
BAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMRgwFgYDVQQKEw9TUERZIFByb3h5
IERlbW8xEjAQBgNVBAMTCWxvY2FsaG9zdDAeFw0xNDEwMTQwNDUwMTJaFw0xNTEw
MTQwNDUwMTJaMFAxCzAJBgNVBAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMRgw
FgYDVQQKEw9TUERZIFByb3h5IERlbW8xEjAQBgNVBAMTCWxvY2FsaG9zdDCCASIw
DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMxv7mqzNMVVFoBjqOSUy2cNM9c6
6gTgVLr9ssoLU0TC4biY1B/KoD7G9Ive6PwfdpipgGY+tuPHfEzBCCHD7exER1NL
npWauo6Lwh3wOjuo5Er6klgBGFuHYx8jJ2jBwCFvTcG2zJRedU/Pby6Fa27X6acw
faAtReG5YOHs8YRmg4ErWqfRucoM3zj8vvMWnushMhYQxo1EVLJ2EvvbHEkip4ap
pro+2Ql0KY4XT3EoMTRHICbolK/uQYoe0musKnwCGPg2NL6e27uvi47G7GrIpcf3
HN4HZMoOzJ8ti7IIEkF0fVTgQEVkluInfned69WCwxecMQZs5sdBuwE3Kh0CAwEA
AaOBszCBsDAdBgNVHQ4EFgQU86bqiYciIYDN+KAPlnJL6tSbH6IwgYAGA1UdIwR5
MHeAFPOm6omHIiGAzfigD5ZyS+rUmx+ioVSkUjBQMQswCQYDVQQGEwJBVTETMBEG
A1UECBMKU29tZS1TdGF0ZTEYMBYGA1UEChMPU1BEWSBQcm94eSBEZW1vMRIwEAYD
VQQDEwlsb2NhbGhvc3SCCQDY21SE01amgDAMBgNVHRMEBTADAQH/MA0GCSqGSIb3
DQEBBQUAA4IBAQCkcr0DLPCbP5l9G0YI/XKVsUW9fXcTvge6Eko0R8qAkzTcsZQv
DbKcIM3z52QguCuJ9k63X4p174FKq7+qmieqaifosGKV03pyyxWLMpRooUUVXEBM
gZaRfp9VG2N4zrRaIklOSkAscnwybv2U3LZhKDlc7Yatsr1/TFkbCnzll514UnTz
ewjrlzVitUSEkwEGvLhKQuVPM9/3MAm+ztFpx846/GZ2XJSAFQLtHudjMXnFLihA
7nGZvE4rudyT70YsKu0BP0KjVZXrxTh81C4kyJu9xo4YuiDCFtvwtjoty0ygbuQN
a38i0bxFlYFmbWHooNCPUWVy59MOnW9zxaTV
-----END CERTIFICATE-----
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAzG/uarM0xVUWgGOo5JTLZw0z1zrqBOBUuv2yygtTRMLhuJjU
H8qgPsb0i97o/B92mKmAZj6248d8TMEIIcPt7ERHU0uelZq6jovCHfA6O6jkSvqS
WAEYW4djHyMnaMHAIW9NwbbMlF51T89vLoVrbtfppzB9oC1F4blg4ezxhGaDgSta
p9G5ygzfOPy+8xae6yEyFhDGjURUsnYS+9scSSKnhqmmuj7ZCXQpjhdPcSgxNEcg
JuiUr+5Bih7Sa6wqfAIY+DY0vp7bu6+Ljsbsasilx/cc3gdkyg7Mny2LsggSQXR9
VOBARWSW4id+d53r1YLDF5wxBmzmx0G7ATcqHQIDAQABAoIBACybj85AZBdaxZom
JMgbn3ZQ7yrbdAy0Vkim6sgjSHwMeewpjL+TGvwXtWx/qx64Tsxoz9d/f7Cb6odk
5z1W3ydajqWiLmw+Ys6PuD+IF2zFIWsq2ZvSQVpXZE17AjJddGrXOoQ2OtV09uv/
OydPfW2mNxl//ylgN4tVQ8qIRPq6b1GWWZvjTw4K3jPrlAifobYBBR+BSk446O7F
iGvax5lNNCDMN2y+6hlnhlTHuvc0DXQA0XBhWTNYu8BNNrvC3I31RmxdY7Frm7IA
RUGy/l2kLHCRCTF8Q0C4ydpE5ZFgpxkWK7p3QEv/gnVAwsOSN/nThdoorWWHTbNl
pA5l1RECgYEA/ASaS9mqWWthUkOW51L6c7IIiRPAhrbPnx1mkAtUPeehHn1+G8Qu
upUEXslWokhmQ3UAGhrId6FVYsfftNPMNck9mv4ntW7MoZLXZqTiFSqx4pQTjoYg
PQ4c/jrQLsmislcKTiVx6kFYFcnI1ayXXEtaby0lri8XsAR5F90OpycCgYEAz6re
DR5EZZKx61wyEfuPWE6aACWlEqlbTa8nTMddxnZUZXtzbwRFapGfs+uHCURF0dGj
37cl4q8JdGbbYePk9nlOC4RoSw8Zh3fB4yRSZocB4yB047ofpBmt4BigGtgZ5BLZ
zqVREgBUI+tFPPHkMmBY4lCaUsCe11SEwyZFzxsCgYEA3nRNonBy/tVbJZtFw9Eq
BB/9isolooQRxrjUBIgLh01Dmj9ZprbILKhHIEgGsd7IbfkD6wcDNx3w2e3mGJ7v
3fZR69M2R9+Sv3h3rEIU0mxKct8UWDUqldo0W3CcvP/9HgDYttw0rnuZfjoMjhf3
z18wZ3xpi1RES3nXTeox+fcCgYBlPxkjrC4Ml4jHBxwiSFOK6keK6s+gWZF6Pnsa
o9jEecyL7bRJ2/s8CeOjBKHBkte3hE4xNEn0SwKBDeTHxSRMRrgWRWfTsHjx4yFU
bND/y7LP2XMj1Aq5JwvuxhLJA7Mbz1UBuvfbnu1m1b3cCNMI/JBZRpL25ZKLyVkx
C+fdIQKBgA+tLeF10zqGGc4269b6nQWplc5E/qnIRK0cfnKb9BtffmA4FbjUpZKj
+cGmbtbw7ySkAIKLp4HoJmzkXJageGTSEb/sQIodxMiJCGvvgJmPPnGzU8OiUGAl
VmRjuAQ2eCcsUyvrJYgKW9UWskqSe6z5w/Uxo/sZdHlaGljNdKcn
-----END RSA PRIVATE KEY-----
require 'bundler'
require 'optparse'
require 'socket'
require 'openssl'
require 'uri'
require 'http/2'
options = { port: 8080, secure: true }
OptionParser.new do |opts|
opts.banner = 'Usage: server.rb [options]'
opts.on('-p', '--port [Integer]', 'listen port') do |v|
options[:port] = v
end
end.parse!
puts "Starting HTTP/2 server on port #{options[:port]}"
server = TCPServer.new(options[:port])
if options[:secure]
ctx = OpenSSL::SSL::SSLContext.new
ctx.cert = OpenSSL::X509::Certificate.new(File.open('mycert.pem'))
ctx.key = OpenSSL::PKey::RSA.new(File.open('mykey.pem'))
ctx.npn_protocols = ['h2']
server = OpenSSL::SSL::SSLServer.new(server, ctx)
end
loop do
sock = server.accept
puts 'New TCP connection!'
conn = HTTP2::Server.new
conn.on(:frame) do |bytes|
sock.write bytes
end
conn.on(:frame_sent) do |frame|
puts "Sent frame: #{frame.inspect}"
end
conn.on(:frame_received) do |frame|
puts "Received frame: #{frame.inspect}"
end
conn.on(:stream) do |stream|
log = Proc.new {|msg| puts "[stream #{stream.id}]: #{msg}"}
req, buffer = {}, ''
stream.on(:active) { log.call 'client opened new stream' }
stream.on(:close) { log.call 'stream closed' }
stream.on(:headers) { |h| req = Hash[*h.flatten] }
stream.on(:data) { |d| buffer << d }
stream.on(:half_close) do
if req[':path'] == '/'
stream.promise({
':authority' => 'localhost:8080',
':method' => 'GET',
':scheme' => 'https',
':path' => '/sw.js'
}) do |push|
push.headers({
':status' => '200',
'content-type' => 'text/javascript'
})
push.data(IO.read('sw.js'))
end
end
stream.headers({
':status' => '200',
'content-type' => 'text/html',
}, end_stream: false)
stream.data(IO.read('takeover.html'))
end
end
while !sock.closed? && !(sock.eof? rescue true)
data = sock.readpartial(1024)
begin
conn << data
rescue => e
puts "Exception: #{e}, #{e.message} - closing socket."
sock.close
end
end
end
self.oninstall = function() {
console.log("oninstall");
self.skipWaiting();
};
self.onactivate = function() {
console.log("onactivate");
clients.claim();
};
self.onmessage = function(event) {
if (event.data == 'claim') {
clients.claim();
}
};
self.onfetch = function(event) {
var url = new URL(event.request.url);
if (url.host === 'sw') {
event.respondWith(
new Response('body {background: #ccc}', {
headers: {
"Content-Type": "text/css"
}
})
);
}
};
<!DOCTYPE html>
<html>
<head>
<style>
body {
white-space: pre;
font-family: monospace;
font-size: 14px;
}
</style>
</head>
<body>
<h2>Service Worker Status</h2>
<ul>
<li>Availability: <strong id="availability"></strong></li>
<li>In control: <strong id="controlled"></strong></li>
<li>Registration: <strong id="register"></strong></li>
<li>Initial status: <strong id="kind"></strong></li>
</p>
<ol id="states"></ol>
<script>
function logState(state) {
var liElement = document.createElement('li');
liElement.textContent = state + ": " + performance.now();
document.querySelector('#states').appendChild(liElement);
document.querySelector('#controlled').textContent = navigator.serviceWorker.controller ? 'yes' : 'no';
}
logState('executing inline script');
if ('serviceWorker' in navigator) {
document.querySelector('#availability').textContent = 'supported';
navigator.serviceWorker.register('sw.js', {scope: './'}).then(function(registration) {
document.querySelector('#register').textContent = 'succeeded';
var serviceWorker;
if (registration.installing) {
serviceWorker = registration.installing;
document.querySelector('#kind').textContent = 'installing';
} else if (registration.waiting) {
serviceWorker = registration.waiting;
document.querySelector('#kind').textContent = 'waiting';
} else if (registration.active) {
serviceWorker = registration.active;
document.querySelector('#kind').textContent = 'active';
}
if (serviceWorker) {
logState(serviceWorker.state);
serviceWorker.addEventListener('statechange', function(e) {
logState(e.target.state);
});
}
}).catch(function(error) {
// Something went wrong during registration. The service-worker.js file
// might be unavailable or contain a syntax error.
document.querySelector('#register').textContent = 'failed: ' + error;
});
} else {
document.querySelector('#availability').textContent = 'not supported';
}
</script>
<!--SPLIT-->
<link href="//sw/style.css" rel="stylesheet">
<p>body content</p>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment