mirror of
https://github.com/basecamp/once-campfire.git
synced 2026-10-08 07:40:08 +09:00
53520d4444
Since push delivery was pinned to the IP resolved and guarded for it, every push opens a new TCP and TLS connection: Net::HTTP::Persistent looks the host up itself and can't be pinned. The handshake is one or two extra round trips for every push. WebPush::Connections keeps the pinned connections open for 30 seconds and hands one out again only to a delivery whose own, fresh resolution returned the same address for the same host. Net::HTTP only ever reconnects to that address, so no request goes to an address the guard didn't just approve. A connection the push service closed while idle is replaced before the push is written; once it's written, a dropped connection raises ConnectionLost instead of sending the push twice or invalidating the subscription. A delivery without a resolved IP is no longer sent at all, and net-http-persistent goes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0142qgjggdJ2KDdGk7RF9Xm9
52 lines
2.0 KiB
Ruby
52 lines
2.0 KiB
Ruby
require "web-push"
|
|
require "web_push/connections"
|
|
require "web_push/pool"
|
|
require "web_push/notification"
|
|
|
|
Rails.application.configure do
|
|
config.x.web_push_pool = WebPush::Pool.new(
|
|
invalid_subscription_handler: ->(subscription_id) do
|
|
Rails.application.executor.wrap do
|
|
Rails.logger.info "Destroying push subscription: #{subscription_id}"
|
|
Push::Subscription.find_by(id: subscription_id)&.destroy
|
|
end
|
|
end
|
|
)
|
|
|
|
at_exit { config.x.web_push_pool.shutdown }
|
|
end
|
|
|
|
module WebPush::PersistentRequest
|
|
def perform
|
|
# Pin the connection to the public IP resolved (and guarded) by
|
|
# Push::Subscription so delivery can't be rebound to a private address
|
|
# between resolution and connect. There is no unpinned path: a delivery
|
|
# without a resolved IP is not sent.
|
|
endpoint_ip = @options[:endpoint_ip] or raise ArgumentError, "Push deliveries must be pinned to a resolved endpoint IP"
|
|
|
|
# The explicit nil proxy address disables proxy discovery from
|
|
# http_proxy/https_proxy. An egress proxy would open the TCP connection
|
|
# itself and re-resolve the endpoint host, so http.ipaddr would no longer
|
|
# pin the destination and the DNS-rebinding guarantee would be lost.
|
|
# Push delivery to public vendor endpoints goes direct.
|
|
http = Net::HTTP.new(uri.host, uri.port, nil)
|
|
http.ipaddr = endpoint_ip
|
|
http.use_ssl = true
|
|
http.ssl_timeout = @options[:ssl_timeout] unless @options[:ssl_timeout].nil?
|
|
http.open_timeout = @options[:open_timeout] unless @options[:open_timeout].nil?
|
|
http.read_timeout = @options[:read_timeout] unless @options[:read_timeout].nil?
|
|
|
|
req = Net::HTTP::Post.new(uri.request_uri, headers)
|
|
req.body = body
|
|
|
|
# WebPush::Connections reuses an open connection only for this same host
|
|
# and pinned IP, so the guarantee holds for every request it sends.
|
|
resp = @options[:connection] ? @options[:connection].request(http, req) : http.request(req)
|
|
verify_response(resp)
|
|
|
|
resp
|
|
end
|
|
end
|
|
|
|
WebPush::Request.prepend WebPush::PersistentRequest
|