通过 Apache/Varnish 的 Websocket 失败
Websocket via Apache/Varnish failed
几天来我一直在尝试让这个与 websocket 的连接正常工作,但是当我尝试通过 Javascript 连接到它时仍然遇到错误。
javascript 抛出的错误如下:
WebSocket connection to 'ws://lmc-staging.mydomain.org:8081/' failed: Connection closed before receiving a handshake response
brainsocket.min.js:1 Event {clipboardData: undefined, path: NodeList[0], cancelBubble: false, returnValue: true, srcElement: WebSocket…}bubbles: false cancelBubble: false cancelable: false clipboardData: undefined currentTarget: WebSocket defaultPrevented: false eventPhase: 0path: NodeList[0] returnValue: true srcElement: WebSockettarget: WebSockettimeStamp: 1421938720697 type: "error"__proto__: Event
brainsocket.min.js:1 WebSocket is already in CLOSING or CLOSED state.
请求是 piped
使用 Varnish 到 Apache 网络服务器 (xxx.xxx.xxx.203)。清漆配置发布如下:
# This is a basic VCL configuration file for varnish. See the vcl(7)
# man page for details on VCL syntax and semantics.
#
# Default backend definition. Set this to point to your content
# server.
#
# Internal hosts; same as purge, but separated for clarity.
acl internal {
"localhost";
"127.0.0.1";
"xxx.xxx.xxx.0"/24;
"xxx.xxx.xxx.203";
"xxx.xxx.xxx.204";
}
# Hosts allowed to purge cache
acl purge {
"localhost";
"127.0.0.1";
"xxx.xxx.xxx.0"/24;
"xxx.xxx.xxx.203";
"xxx.xxx.xxx.204";
}
# DEV webserver
backend vsrv1474 {
.host = "xxx.xxx.xxx.203";
.port = "80";
.connect_timeout = 300s;
.first_byte_timeout = 300s;
.between_bytes_timeout = 300s;
}
# DEV brainsocket
backend brainsocket {
.host = "xxx.xxx.xxx.203";
.port = "8081";
.connect_timeout = 300s;
.first_byte_timeout = 300s;
.between_bytes_timeout = 300s;
}
# PROD webserver
backend vsrv1475 {
.host = "xxx.xxx.xxx.204";
.port = "80";
#.connect_timeout = 5s;
#.first_byte_timeout = 60s;a
#.between_bytes_timeout = 60s;
.connect_timeout = 300s;
.first_byte_timeout = 300s;
.between_bytes_timeout = 300s;
}
sub vcl_recv {
# Allow purge from hosts in purge acl or return 405
if (req.request == "PURGE") {
if (!client.ip ~ purge) {
error 405 "Not allowed";
}
return(lookup);
}
# entry for brainsocket
if (req.http.Upgrade ~ "(?i)websocket") {
set req.backend = brainsocket;
return (pipe);
}
# end entry for brainsocket
if (req.url ~ "/dev/" || req.http.host == "staging.mydomain.org" || req.http.host == "dev.mydomain2.org" || req.http.host == "lmc-staging.mydomain.org" ){
set req.backend = vsrv1474;
return(pass);
} elsif ( req.http.host == "www.mydomain2.org" || req.http.host == "lmc.mydomain2.org" ) {
set req.backend = vsrv1475;
} else {
set req.backend = vsrv1475;
}
if (req.url ~ "^/misc/progress\.js\?[0-9]+$") {
set req.url = "/misc/progress.js";
}
if (req.url ~ "^/admin/content/backup_migrate/export") {
return (pipe);
}
# Do not cache these paths.
if(req.url ~ "^/status\.php$"||
req.url ~ "^/update\.php$"||
req.url ~ "^/install\.php" ||
req.url ~ "^/admin" ||
req.url ~ "^/admin/.*$"||
req.url ~ "^/user" ||
req.url ~ "^/user/.*$" ||
req.url ~ "^/users/.*$" ||
req.url ~ "^/info/.*$" ||
req.url ~ "^/flag/.*$"||
req.url ~ "^.*/ajax/.*$"||
req.url ~ "^.*/facebook-rss.xml$" ||
req.url ~ "^.*/twitter-rss.xml$" ||
req.url ~ "^.*/ahah/.*$") {
return(pass);
}
# Disallow outside access to cron.php or install.php
if (req.url ~ "^/(cron|install)\.php$" && !client.ip ~ internal) {
error 404 "Page not found.";
}
# Always cache the following file types for all users.
if (req.url ~ "(?i)\.(png|gif|jpeg|jpg|ico|swf|css|js|html|htm|woff)(\?[a-z0-9]+)?$") {
unset req.http.Cookie;
}
# Remove all cookies that Drupal doesn't need to know about. ANY remaining
# cookie will cause the request to pass-through to Apache. For the most part
# we always set the NO_CACHE cookie after any POST request, disabling the
# Varnish cache temporarily. The session cookie allows all authenticated users
# to pass through as long as they're logged in.
if (req.http.Cookie) {
set req.http.Cookie = ";" + req.http.Cookie;
set req.http.Cookie = regsuball(req.http.Cookie, "; +", ";");
set req.http.Cookie = regsuball(req.http.Cookie, ";(S{1,2}ESS[a-z0-9]+|NO_CACHE|CI_SESSION|ci_session|token|PHPSESSID)=", "; =");
set req.http.Cookie = regsuball(req.http.Cookie, ";[^ ][^;]*", "");
set req.http.Cookie = regsuball(req.http.Cookie, "^[; ]+|[; ]+$", "");
if (req.http.Cookie == "") {
# If there are no remaining cookies, remove the cookie header. If there
# aren't any cookie headers, Varnish's default behavior will be to cache
# the page.
unset req.http.Cookie;
}
else {
# If there is any cookies left (a session or NO_CACHE cookie), do not
# cache the page. Pass it on to Apache directly.
return (pass);
}
}
# Remove the "has_js" cookie
set req.http.Cookie = regsuball(req.http.Cookie, "has_js=[^;]+(; )?", "");
# Remove the "Drupal.toolbar.collapsed" cookie
set req.http.Cookie = regsuball(req.http.Cookie, "Drupal.toolbar.collapsed=[^;]+(; )?", "");
# Remove any Google Analytics based cookies
set req.http.Cookie = regsuball(req.http.Cookie, "__utm.=[^;]+(; )?", "");
# Remove the Quant Capital cookies (added by some plugin, all __qca)
set req.http.Cookie = regsuball(req.http.Cookie, "__qc.=[^;]+(; )?", "");
# Are there cookies left with only spaces or that are empty?
if (req.http.cookie ~ "^ *$") {
unset req.http.cookie;
}
# Cache static content unique to the theme (so no user uploaded images)
if (req.url ~ "^/themes/" && req.url ~ ".(css|js|png|gif|jp(e)?g)") {
unset req.http.cookie;
}
}
sub vcl_pipe {
#copy the upgrade header
if (req.http.upgrade) {
set bereq.http.upgrade = req.http.upgrade;
}
#closing the connection might be necessary for some applications as the connection will remain open forever and consume resources if not properly stopped by server or client
#set bereq.http.Connection = "close";
}
sub vcl_miss {
if (req.request == "PURGE") {
purge;
error 200 "Purged";
}
}
sub vcl_hit {
if (req.request == "PURGE") {
purge;
error 200 "Purged";
}
}
sub vcl_fetch {
unset beresp.http.Vary;
set beresp.http.x-url = req.url;
set beresp.http.x-host = req.http.host;
# Don't allow static files to set cookies.
if (req.url ~ "(?i)\.(png|gif|jpeg|jpg|ico|swf|css|js|html|htm)(\?[a-z0-9]+)?$") {
# beresp == Back-end response from the web server.
unset beresp.http.set-cookie;
}
# Check if no ttl and a cookie set.
if (beresp.ttl <= 0s && beresp.http.Cookie ~ ".+" ) {
set beresp.http.X-Cacheable = "NO:Not Cacheable: No ttl; has cookie";
# Varnish determined the object was not cacheable
} elsif (beresp.ttl <= 0s) {
set beresp.http.X-Cacheable = "NO:Not Cacheable: No ttl; no cookie";
# You don't wish to cache content for logged in users
} elsif (req.http.Cookie ~ "(S{1,2}ESS[a-z0-9]+|NO_CACHE|CI_SESSION|ci_session|token|PHPSESSID)") {
set beresp.http.X-Cacheable = "NO:Got Session";
return(hit_for_pass);
# You are respecting the Cache-Control=private header from the backend
} elsif (beresp.http.Cache-Control ~ "private") {
set beresp.http.X-Cacheable = "NO:Cache-Control=private";
return(hit_for_pass);
# Varnish determined the object was cacheable
} else {
set beresp.http.X-Cacheable = "YES";
}
return(deliver);
}
sub vcl_deliver {
if (obj.hits > 0) {
set resp.http.X-Cache = "HIT";
} else {
set resp.http.X-Cache = "MISS";
}
unset resp.http.x-url; # Optional
unset resp.http.x-host; # Optional
unset resp.http.Server;
unset resp.http.X-Generator;
unset resp.http.X-Powered-By;
unset resp.http.X-Drupal-Cache;
}
我正在使用 brainsocket 包,它允许您使用 Laravel 创建客户端和服务器端套接字。 Brainsocket 使用 Ratchet 作为其底层 websocket 层。
问题是(正如 Marcel Dumont 所建议的那样)与 Varnish 未配置为侦听端口 8081 这一事实有关。因此我们将前端客户端重定向到连接端口 80 并依靠 Varnish 通过管道传输信息到端口 8081 上的底层 websocket。
几天来我一直在尝试让这个与 websocket 的连接正常工作,但是当我尝试通过 Javascript 连接到它时仍然遇到错误。
javascript 抛出的错误如下:
WebSocket connection to 'ws://lmc-staging.mydomain.org:8081/' failed: Connection closed before receiving a handshake response
brainsocket.min.js:1 Event {clipboardData: undefined, path: NodeList[0], cancelBubble: false, returnValue: true, srcElement: WebSocket…}bubbles: false cancelBubble: false cancelable: false clipboardData: undefined currentTarget: WebSocket defaultPrevented: false eventPhase: 0path: NodeList[0] returnValue: true srcElement: WebSockettarget: WebSockettimeStamp: 1421938720697 type: "error"__proto__: Event
brainsocket.min.js:1 WebSocket is already in CLOSING or CLOSED state.
请求是 piped
使用 Varnish 到 Apache 网络服务器 (xxx.xxx.xxx.203)。清漆配置发布如下:
# This is a basic VCL configuration file for varnish. See the vcl(7)
# man page for details on VCL syntax and semantics.
#
# Default backend definition. Set this to point to your content
# server.
#
# Internal hosts; same as purge, but separated for clarity.
acl internal {
"localhost";
"127.0.0.1";
"xxx.xxx.xxx.0"/24;
"xxx.xxx.xxx.203";
"xxx.xxx.xxx.204";
}
# Hosts allowed to purge cache
acl purge {
"localhost";
"127.0.0.1";
"xxx.xxx.xxx.0"/24;
"xxx.xxx.xxx.203";
"xxx.xxx.xxx.204";
}
# DEV webserver
backend vsrv1474 {
.host = "xxx.xxx.xxx.203";
.port = "80";
.connect_timeout = 300s;
.first_byte_timeout = 300s;
.between_bytes_timeout = 300s;
}
# DEV brainsocket
backend brainsocket {
.host = "xxx.xxx.xxx.203";
.port = "8081";
.connect_timeout = 300s;
.first_byte_timeout = 300s;
.between_bytes_timeout = 300s;
}
# PROD webserver
backend vsrv1475 {
.host = "xxx.xxx.xxx.204";
.port = "80";
#.connect_timeout = 5s;
#.first_byte_timeout = 60s;a
#.between_bytes_timeout = 60s;
.connect_timeout = 300s;
.first_byte_timeout = 300s;
.between_bytes_timeout = 300s;
}
sub vcl_recv {
# Allow purge from hosts in purge acl or return 405
if (req.request == "PURGE") {
if (!client.ip ~ purge) {
error 405 "Not allowed";
}
return(lookup);
}
# entry for brainsocket
if (req.http.Upgrade ~ "(?i)websocket") {
set req.backend = brainsocket;
return (pipe);
}
# end entry for brainsocket
if (req.url ~ "/dev/" || req.http.host == "staging.mydomain.org" || req.http.host == "dev.mydomain2.org" || req.http.host == "lmc-staging.mydomain.org" ){
set req.backend = vsrv1474;
return(pass);
} elsif ( req.http.host == "www.mydomain2.org" || req.http.host == "lmc.mydomain2.org" ) {
set req.backend = vsrv1475;
} else {
set req.backend = vsrv1475;
}
if (req.url ~ "^/misc/progress\.js\?[0-9]+$") {
set req.url = "/misc/progress.js";
}
if (req.url ~ "^/admin/content/backup_migrate/export") {
return (pipe);
}
# Do not cache these paths.
if(req.url ~ "^/status\.php$"||
req.url ~ "^/update\.php$"||
req.url ~ "^/install\.php" ||
req.url ~ "^/admin" ||
req.url ~ "^/admin/.*$"||
req.url ~ "^/user" ||
req.url ~ "^/user/.*$" ||
req.url ~ "^/users/.*$" ||
req.url ~ "^/info/.*$" ||
req.url ~ "^/flag/.*$"||
req.url ~ "^.*/ajax/.*$"||
req.url ~ "^.*/facebook-rss.xml$" ||
req.url ~ "^.*/twitter-rss.xml$" ||
req.url ~ "^.*/ahah/.*$") {
return(pass);
}
# Disallow outside access to cron.php or install.php
if (req.url ~ "^/(cron|install)\.php$" && !client.ip ~ internal) {
error 404 "Page not found.";
}
# Always cache the following file types for all users.
if (req.url ~ "(?i)\.(png|gif|jpeg|jpg|ico|swf|css|js|html|htm|woff)(\?[a-z0-9]+)?$") {
unset req.http.Cookie;
}
# Remove all cookies that Drupal doesn't need to know about. ANY remaining
# cookie will cause the request to pass-through to Apache. For the most part
# we always set the NO_CACHE cookie after any POST request, disabling the
# Varnish cache temporarily. The session cookie allows all authenticated users
# to pass through as long as they're logged in.
if (req.http.Cookie) {
set req.http.Cookie = ";" + req.http.Cookie;
set req.http.Cookie = regsuball(req.http.Cookie, "; +", ";");
set req.http.Cookie = regsuball(req.http.Cookie, ";(S{1,2}ESS[a-z0-9]+|NO_CACHE|CI_SESSION|ci_session|token|PHPSESSID)=", "; =");
set req.http.Cookie = regsuball(req.http.Cookie, ";[^ ][^;]*", "");
set req.http.Cookie = regsuball(req.http.Cookie, "^[; ]+|[; ]+$", "");
if (req.http.Cookie == "") {
# If there are no remaining cookies, remove the cookie header. If there
# aren't any cookie headers, Varnish's default behavior will be to cache
# the page.
unset req.http.Cookie;
}
else {
# If there is any cookies left (a session or NO_CACHE cookie), do not
# cache the page. Pass it on to Apache directly.
return (pass);
}
}
# Remove the "has_js" cookie
set req.http.Cookie = regsuball(req.http.Cookie, "has_js=[^;]+(; )?", "");
# Remove the "Drupal.toolbar.collapsed" cookie
set req.http.Cookie = regsuball(req.http.Cookie, "Drupal.toolbar.collapsed=[^;]+(; )?", "");
# Remove any Google Analytics based cookies
set req.http.Cookie = regsuball(req.http.Cookie, "__utm.=[^;]+(; )?", "");
# Remove the Quant Capital cookies (added by some plugin, all __qca)
set req.http.Cookie = regsuball(req.http.Cookie, "__qc.=[^;]+(; )?", "");
# Are there cookies left with only spaces or that are empty?
if (req.http.cookie ~ "^ *$") {
unset req.http.cookie;
}
# Cache static content unique to the theme (so no user uploaded images)
if (req.url ~ "^/themes/" && req.url ~ ".(css|js|png|gif|jp(e)?g)") {
unset req.http.cookie;
}
}
sub vcl_pipe {
#copy the upgrade header
if (req.http.upgrade) {
set bereq.http.upgrade = req.http.upgrade;
}
#closing the connection might be necessary for some applications as the connection will remain open forever and consume resources if not properly stopped by server or client
#set bereq.http.Connection = "close";
}
sub vcl_miss {
if (req.request == "PURGE") {
purge;
error 200 "Purged";
}
}
sub vcl_hit {
if (req.request == "PURGE") {
purge;
error 200 "Purged";
}
}
sub vcl_fetch {
unset beresp.http.Vary;
set beresp.http.x-url = req.url;
set beresp.http.x-host = req.http.host;
# Don't allow static files to set cookies.
if (req.url ~ "(?i)\.(png|gif|jpeg|jpg|ico|swf|css|js|html|htm)(\?[a-z0-9]+)?$") {
# beresp == Back-end response from the web server.
unset beresp.http.set-cookie;
}
# Check if no ttl and a cookie set.
if (beresp.ttl <= 0s && beresp.http.Cookie ~ ".+" ) {
set beresp.http.X-Cacheable = "NO:Not Cacheable: No ttl; has cookie";
# Varnish determined the object was not cacheable
} elsif (beresp.ttl <= 0s) {
set beresp.http.X-Cacheable = "NO:Not Cacheable: No ttl; no cookie";
# You don't wish to cache content for logged in users
} elsif (req.http.Cookie ~ "(S{1,2}ESS[a-z0-9]+|NO_CACHE|CI_SESSION|ci_session|token|PHPSESSID)") {
set beresp.http.X-Cacheable = "NO:Got Session";
return(hit_for_pass);
# You are respecting the Cache-Control=private header from the backend
} elsif (beresp.http.Cache-Control ~ "private") {
set beresp.http.X-Cacheable = "NO:Cache-Control=private";
return(hit_for_pass);
# Varnish determined the object was cacheable
} else {
set beresp.http.X-Cacheable = "YES";
}
return(deliver);
}
sub vcl_deliver {
if (obj.hits > 0) {
set resp.http.X-Cache = "HIT";
} else {
set resp.http.X-Cache = "MISS";
}
unset resp.http.x-url; # Optional
unset resp.http.x-host; # Optional
unset resp.http.Server;
unset resp.http.X-Generator;
unset resp.http.X-Powered-By;
unset resp.http.X-Drupal-Cache;
}
我正在使用 brainsocket 包,它允许您使用 Laravel 创建客户端和服务器端套接字。 Brainsocket 使用 Ratchet 作为其底层 websocket 层。
问题是(正如 Marcel Dumont 所建议的那样)与 Varnish 未配置为侦听端口 8081 这一事实有关。因此我们将前端客户端重定向到连接端口 80 并依靠 Varnish 通过管道传输信息到端口 8081 上的底层 websocket。