Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 31 additions & 4 deletions lib/resty/websocket/proxy.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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,
Expand Down Expand Up @@ -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
Expand Down
136 changes: 136 additions & 0 deletions t/09-max-payload-len.t
Original file line number Diff line number Diff line change
@@ -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]