diff --git a/lib/resty/websocket/proxy.lua b/lib/resty/websocket/proxy.lua index bc9b211..3d40b1e 100644 --- a/lib/resty/websocket/proxy.lua +++ b/lib/resty/websocket/proxy.lua @@ -106,10 +106,31 @@ function _M.new(opts) error("opts.upstream_max_fragments must be a number >= 1", 2) end + if opts.client_new_opts ~= nil and type(opts.client_new_opts) ~= "table" then + error("opts.client_new_opts must be a table", 2) + end + + if opts.upstream_new_opts ~= nil and type(opts.upstream_new_opts) ~= "table" then + error("opts.upstream_new_opts must be a table", 2) + end + - -- TODO: provide a means of passing options through to the - -- resty.websocket.client constructor (like `max_payload_len`) - local client, err = ws_client:new() + -- as with client_max_frame_size/upstream_max_fragments above, "client" + -- and "upstream" name the two roles of this proxy, not the two library + -- modules: "client" is the role facing the real, downstream WebSocket + -- client, which this proxy serves through a resty.websocket.server + -- instance (self.server, created in connect_client() below); "upstream" + -- is the role facing the backend, served through a resty.websocket.client + -- instance (self.client, created right here). So opts.client_new_opts + -- ends up at ws_server:new(), and opts.upstream_new_opts at ws_client:new(). + -- + -- Each is passed through as-is to that constructor (same as + -- connect_upstream() already does for connect()'s own options), giving + -- the caller access to everything it supports: max_payload_len, + -- max_recv_len, max_send_len, send_unmasked/send_masked, timeout. Left + -- unset, that constructor keeps defaulting max_payload_len (and, through + -- it, max_recv_len/max_send_len) to 65535, same as it always has. + local client, err = ws_client:new(opts.upstream_new_opts) if not client then return nil, "failed to create client: " .. err end @@ -124,6 +145,9 @@ function _M.new(opts) client_max_fragments = opts.client_max_fragments, upstream_max_frame_size = opts.upstream_max_frame_size, upstream_max_fragments = opts.upstream_max_fragments, + -- passed to ws_server:new() in connect_client(), once the server + -- accepting the downstream client's connection is actually created + client_new_opts = opts.client_new_opts, aggregate_fragments = opts.aggregate_fragments, debug = opts.debug, client_state = _STATES.INIT, @@ -475,7 +499,10 @@ function _M:connect_client() self:dd("completing client handshake") - local server, err = ws_server:new() + -- self.client_new_opts is opts.client_new_opts from _M.new(): the + -- "client" role, i.e. the real downstream client, is served through + -- this resty.websocket.server instance + local server, err = ws_server:new(self.client_new_opts) if not server then return nil, err end diff --git a/t/09-max-payload-len.t b/t/09-max-payload-len.t new file mode 100644 index 0000000..67e93cb --- /dev/null +++ b/t/09-max-payload-len.t @@ -0,0 +1,136 @@ +# vim:set ts=4 sts=4 sw=4 et ft=: + +use lib '.'; +use t::Tests; + +plan tests => repeat_each() * (blocks() * 3); + +run_tests(); + +__DATA__ + +=== TEST 1: a frame over 65535 bytes still kills the connection when no size options are set (unchanged default) +--- http_config eval: $t::Tests::HttpConfig +--- config + location /proxy { + content_by_lua_block { + local proxy = require "resty.websocket.proxy" + + local wp, err = proxy.new() + if not wp then + ngx.log(ngx.ERR, "failed creating proxy: ", err) + return ngx.exit(444) + end + + local ok, err = wp:connect(proxy._tests.echo) + if not ok then + ngx.log(ngx.ERR, err) + return ngx.exit(444) + end + + local done, err = wp:execute() + if not done then + ngx.log(ngx.ERR, "failed proxying: ", err) + return ngx.exit(444) + end + } + } + + location /t { + content_by_lua_block { + local client = require "resty.websocket.client" + -- raise only the test client's own limit, so it can actually + -- put a >65535 byte frame on the wire; the proxy itself is the + -- thing under test here and gets no such option + local wb = assert(client:new({ max_payload_len = 70000 })) + local uri = "ws://127.0.0.1:" .. ngx.var.server_port .. "/proxy" + + assert(wb:connect(uri)) + assert(wb:send_text(string.rep("a", 65536))) + local data, typ, err = wb:recv_frame() + ngx.say(string.format("data: %s, typ: %s, err: %s", + tostring(data), tostring(typ), tostring(err))) + } + } +--- response_body +data: nil, typ: nil, err: failed to receive the first 2 bytes: connection reset by peer +--- grep_error_log eval: qr/\[lua\].*/ +--- grep_error_log_out eval +qr/failed receiving frame from client: exceeding max payload len/ + + + +=== TEST 2: client_new_opts and upstream_new_opts let a frame over 65535 bytes round-trip +--- http_config eval: $t::Tests::HttpConfig +--- config + location /proxy { + content_by_lua_block { + local proxy = require "resty.websocket.proxy" + + local wp, err = proxy.new({ + client_new_opts = { max_payload_len = 100000 }, + upstream_new_opts = { max_payload_len = 100000 }, + }) + if not wp then + ngx.log(ngx.ERR, "failed creating proxy: ", err) + return ngx.exit(444) + end + + local uri = "ws://127.0.0.1:" .. ngx.var.server_port .. "/upstream" + local ok, err = wp:connect(uri) + if not ok then + ngx.log(ngx.ERR, err) + return ngx.exit(444) + end + + local done, err = wp:execute() + if not done then + ngx.log(ngx.ERR, "failed proxying: ", err) + return ngx.exit(444) + end + } + } + + location /upstream { + content_by_lua_block { + local server = require "resty.websocket.server" + + local wb, err = server:new({ max_payload_len = 100000 }) + if not wb then + ngx.log(ngx.ERR, "failed creating server: ", err) + return ngx.exit(444) + end + + local data, typ, err = wb:recv_frame() + if not data then + ngx.log(ngx.ERR, "failed receiving frame: ", err) + return ngx.exit(444) + end + + local bytes, err = wb:send_text(data) + if not bytes then + ngx.log(ngx.ERR, "failed sending frame: ", err) + return ngx.exit(444) + end + } + } + + location /t { + content_by_lua_block { + local client = require "resty.websocket.client" + local wb = assert(client:new({ max_payload_len = 100000 })) + local uri = "ws://127.0.0.1:" .. ngx.var.server_port .. "/proxy" + + local payload = string.rep("a", 100000) + assert(wb:connect(uri)) + assert(wb:send_text(payload)) + local data, typ, err = wb:recv_frame() + ngx.say(string.format("typ: %s, len: %s, echoed: %s", + tostring(typ), data and #data, + tostring(data == payload))) + } + } +--- response_body +typ: text, len: 100000, echoed: true +--- no_error_log +[error]