This is an automated email from the ASF dual-hosted git repository.

membphis pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-apisix.git


The following commit(s) were added to refs/heads/master by this push:
     new e680436  feature: supported to enable Websocket in `upstream` object 
(#1051)
e680436 is described below

commit e6804360d1712d456cfee31b9d6abb8b16cfffba
Author: Lien <lilien1...@gmail.com>
AuthorDate: Tue Jan 14 14:37:24 2020 +0800

    feature: supported to enable Websocket in `upstream` object (#1051)
---
 doc/architecture-design-cn.md |   2 +
 doc/architecture-design.md    |   2 +
 lua/apisix.lua                |  22 ++++-
 lua/apisix/schema_def.lua     |   4 +
 t/lib/server.lua              |   9 ++
 t/node/upstream-websocket.t   | 223 ++++++++++++++++++++++++++++++++++++++++++
 6 files changed, 259 insertions(+), 3 deletions(-)

diff --git a/doc/architecture-design-cn.md b/doc/architecture-design-cn.md
index 968401d..39c4cfc 100644
--- a/doc/architecture-design-cn.md
+++ b/doc/architecture-design-cn.md
@@ -241,6 +241,7 @@ APISIX 的 Upstream 除了基本的复杂均衡算法选择外,还支持对上
 |key             |必需|该选项只有 `type` 是 `chash` 才有效,需要配合 `hash_on` 来使用,通过 
`hash_on` 和 `key` 来查找对应的 node `id`。`hash_on` 设为 `vars` 时,`key` 为必传参数,目前支持的 
Nginx 内置变量有 `uri, server_name, server_addr, request_uri, remote_port, 
remote_addr, query_string, host, hostname, arg_***`,其中 `arg_***` 
是来自URL的请求参数,[Nginx 变量列表](http://nginx.org/en/docs/varindex.html);`hash_on` 设为 
`header` 时, `key` 为必传参数,自定义的 `header name`;`hash_on` 设为 `cookie` 时, `key` 
为必传参数, 自定义的 `cookie name`;`hash_on` 设为 `consumer` 时,`key` 不需 [...]
 |checks          |可选|配置健康检查的参数,详细可参考[health-check](health-check.md)|
 |retries         |可选|使用底层的 Nginx 重试机制将请求传递给下一个上游,默认 APISIX 
会启用重试机制,根据配置的后端节点个数设置重试次数,如果此参数显式被设置将会覆盖系统默认设置的重试次数。|
+|enable_websocket|可选| 是否启用 `websocket`(布尔值),默认不启用|
 
 创建上游对象用例:
 
@@ -259,6 +260,7 @@ curl http://127.0.0.1:9080/apisix/admin/upstreams/2 -X PUT 
-d '
 {
     "type": "chash",
     "key": "remote_addr",
+    "enable_websocket": true,
     "nodes": {
         "127.0.0.1:80": 1,
         "foo.com:80": 2
diff --git a/doc/architecture-design.md b/doc/architecture-design.md
index 4ce536a..d678da9 100644
--- a/doc/architecture-design.md
+++ b/doc/architecture-design.md
@@ -236,6 +236,7 @@ In addition to the basic complex equalization algorithm 
selection, APISIX's Upst
 |key             |required|This option is only valid if the `type` is `chash`. 
Find the corresponding node `id` according to `hash_on` and `key`. When 
`hash_on` is set as `vars`, `key` is the required parameter, for now, it 
support nginx built-in variables like `uri, server_name, server_addr, 
request_uri, remote_port, remote_addr, query_string, host, hostname, arg_***`, 
`arg_***` is arguments in the request line, [Nginx variables 
list](http://nginx.org/en/docs/varindex.html). When `hash_ [...]
 |checks          |optional|Configure the parameters of the health check. For 
details, refer to [health-check](health-check.md).|
 |retries         |optional|Pass the request to the next upstream using the 
underlying Nginx retry mechanism, the retry mechanism is enabled by default and 
set the number of retries according to the number of backend nodes. If 
`retries` option is explicitly set, it will override the default value.|
+|enable_websocket|optional| enable `websocket`(boolean), default `false`.|
 
 Create an upstream object use case:
 
@@ -254,6 +255,7 @@ curl http://127.0.0.1:9080/apisix/admin/upstreams/2 -X PUT 
-d '
 {
     "type": "chash",
     "key": "remote_addr",
+    "enable_websocket": true,
     "nodes": {
         "127.0.0.1:80": 1,
         "foo.com:80": 2
diff --git a/lua/apisix.lua b/lua/apisix.lua
index 63319cd..2f203b1 100644
--- a/lua/apisix.lua
+++ b/lua/apisix.lua
@@ -304,6 +304,7 @@ function _M.http_access_phase()
         api_ctx.conf_id = route.value.id
     end
 
+    local enable_websocket
     local up_id = route.value.upstream_id
     if up_id then
         local upstreams_etcd = core.config.fetch_created_obj("/upstreams")
@@ -313,11 +314,26 @@ function _M.http_access_phase()
                 parsed_domain(upstream, api_ctx.conf_version,
                               parse_domain_in_up, upstream)
             end
+
+            if upstream.value.enable_websocket then
+                enable_websocket = true
+            end
+        end
+
+    else
+        if route.has_domain then
+            route = parsed_domain(route, api_ctx.conf_version,
+                                  parse_domain_in_route, route)
+        end
+
+        if route.value.upstream and route.value.upstream.enable_websocket then
+            enable_websocket = true
         end
+    end
 
-    elseif route.has_domain then
-        route = parsed_domain(route, api_ctx.conf_version,
-                              parse_domain_in_route, route)
+    if enable_websocket then
+        api_ctx.var.upstream_upgrade    = api_ctx.var.http_upgrade
+        api_ctx.var.upstream_connection = api_ctx.var.http_connection
     end
 
     local plugins = core.tablepool.fetch("plugins", 32, 0)
diff --git a/lua/apisix/schema_def.lua b/lua/apisix/schema_def.lua
index f5544d9..7874882 100644
--- a/lua/apisix/schema_def.lua
+++ b/lua/apisix/schema_def.lua
@@ -273,6 +273,10 @@ local upstream_schema = {
             description = "the key of chash for dynamic load balancing",
             type = "string",
         },
+        enable_websocket = {
+            description = "enable websocket for request",
+            type        = "boolean"
+        },
         desc = {type = "string", maxLength = 256},
         id = id_schema
     },
diff --git a/t/lib/server.lua b/t/lib/server.lua
index 5c59c35..60f57d9 100644
--- a/t/lib/server.lua
+++ b/t/lib/server.lua
@@ -112,6 +112,15 @@ function _M.mock_zipkin()
     end
 end
 
+function _M.websocket_handshake()
+    local websocket = require "resty.websocket.server"
+    local wb, err = websocket:new()
+    if not wb then
+        ngx.log(ngx.ERR, "failed to new websocket: ", err)
+        return ngx.exit(400)
+    end
+end
+
 
 function _M.go()
     local action = string.sub(ngx.var.uri, 2)
diff --git a/t/node/upstream-websocket.t b/t/node/upstream-websocket.t
new file mode 100644
index 0000000..99d9461
--- /dev/null
+++ b/t/node/upstream-websocket.t
@@ -0,0 +1,223 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+use t::APISIX 'no_plan';
+
+repeat_each(1);
+log_level('info');
+worker_connections(256);
+no_root_location();
+no_shuffle();
+
+run_tests();
+
+__DATA__
+
+=== TEST 1: set upstream with websocket (id: 1)
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin").test
+            local code, body = t('/apisix/admin/routes/1',
+                 ngx.HTTP_PUT,
+                 [[{
+                        "upstream": {
+                            "nodes": {
+                                "127.0.0.1:1980": 1
+                            },
+                            "enable_websocket": true,
+                            "type": "roundrobin"
+                        },
+                        "uri": "/websocket_handshake"
+                }]]
+                )
+
+            if code >= 300 then
+                ngx.status = code
+            end
+            ngx.say(body)
+        }
+    }
+--- request
+GET /t
+--- response_body
+passed
+--- no_error_log
+[error]
+
+
+
+=== TEST 2: send websocket
+--- raw_request eval
+"GET /websocket_handshake HTTP/1.1\r
+Host: server.example.com\r
+Upgrade: websocket\r
+Connection: Upgrade\r
+Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==\r
+Sec-WebSocket-Protocol: chat\r
+Sec-WebSocket-Version: 13\r
+Origin: http://example.com\r
+\r
+"
+--- response_headers
+Upgrade: websocket
+Connection: upgrade
+Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk=
+Sec-WebSocket-Protocol: chat
+!Content-Type
+--- raw_response_headers_like: ^HTTP/1.1 101 Switching Protocols\r\n
+--- response_body
+--- no_error_log
+[error]
+--- error_code: 101
+
+
+
+=== TEST 3: set upstream(id: 6)
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin").test
+            local code, body = t('/apisix/admin/upstreams/6',
+                 ngx.HTTP_PUT,
+                 [[{
+                    "nodes": {
+                        "127.0.0.1:1981": 1
+                    },
+                    "type": "roundrobin",
+                    "enable_websocket": true,
+                    "desc": "new upstream"
+                }]]
+                )
+
+            if code >= 300 then
+                ngx.status = code
+            end
+            ngx.say(body)
+        }
+    }
+--- request
+GET /t
+--- response_body
+passed
+--- no_error_log
+[error]
+
+
+
+=== TEST 4: set route with upstream websocket enabled(id: 6)
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin").test
+            local code, body = t('/apisix/admin/routes/6',
+                 ngx.HTTP_PUT,
+                 [[{
+                        "uri": "/websocket_handshake/route",
+                        "upstream_id": "6"
+                }]]
+                )
+
+            if code >= 300 then
+                ngx.status = code
+            end
+            ngx.say(body)
+        }
+    }
+--- request
+GET /t
+--- response_body
+passed
+--- no_error_log
+[error]
+
+
+
+=== TEST 5: send success websocket to upstream
+--- raw_request eval
+"GET /websocket_handshake/route HTTP/1.1\r
+Host: server.example.com\r
+Upgrade: websocket\r
+Connection: Upgrade\r
+Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==\r
+Sec-WebSocket-Protocol: chat\r
+Sec-WebSocket-Version: 13\r
+Origin: http://example.com\r
+\r
+"
+--- response_headers
+Upgrade: websocket
+Connection: upgrade
+Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk=
+Sec-WebSocket-Protocol: chat
+!Content-Type
+--- raw_response_headers_like: ^HTTP/1.1 101 Switching Protocols\r\n
+--- response_body
+--- no_error_log
+[error]
+--- error_code: 101
+
+
+
+=== TEST 6: disable websocket(id: 6)
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin").test
+            local code, body = t('/apisix/admin/upstreams/6',
+                 ngx.HTTP_PUT,
+                 [[{
+                    "nodes": {
+                        "127.0.0.1:1981": 1
+                    },
+                    "type": "roundrobin",
+                    "enable_websocket": false,
+                    "desc": "new upstream"
+                }]]
+                )
+
+            if code >= 300 then
+                ngx.status = code
+            end
+            ngx.say(body)
+        }
+    }
+--- request
+GET /t
+--- response_body
+passed
+--- no_error_log
+[error]
+
+
+
+=== TEST 7: send websocket to upstream without header support
+--- raw_request eval
+"GET /websocket_handshake/route HTTP/1.1\r
+Host: server.example.com\r
+Upgrade: websocket\r
+Connection: Upgrade\r
+Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==\r
+Sec-WebSocket-Protocol: chat\r
+Sec-WebSocket-Version: 13\r
+Origin: http://example.com\r
+\r
+"
+[error]
+--- error_code: 400
+--- grep_error_log eval
+qr/failed to new websocket: bad "upgrade" request header: nil/
+--- grep_error_log_out

Reply via email to