From 06db3ffe3365af1c5b4fffced1bf560d9244461e Mon Sep 17 00:00:00 2001 From: jason Date: Mon, 22 Nov 2021 23:25:46 +0800 Subject: [PATCH 1/3] Ftr: move getty's example to dubbo-getty --- examples/LICENSE | 191 + examples/build_all.sh | 13 + examples/echo/tcp-echo/change_log.md | 37 + examples/echo/tcp-echo/client/app/client.go | 172 + examples/echo/tcp-echo/client/app/config.go | 175 + examples/echo/tcp-echo/client/app/echo.go | 151 + examples/echo/tcp-echo/client/app/handler.go | 91 + examples/echo/tcp-echo/client/app/main.go | 208 ++ .../echo/tcp-echo/client/app/readwriter.go | 82 + .../echo/tcp-echo/client/assembly/bin/load.sh | 186 + .../client/assembly/common/app.properties | 17 + .../tcp-echo/client/assembly/common/build.sh | 77 + .../tcp-echo/client/assembly/linux/dev.sh | 28 + .../tcp-echo/client/assembly/linux/release.sh | 27 + .../tcp-echo/client/assembly/linux/test.sh | 28 + .../echo/tcp-echo/client/assembly/mac/dev.sh | 28 + .../tcp-echo/client/assembly/mac/release.sh | 27 + .../echo/tcp-echo/client/assembly/mac/test.sh | 28 + .../tcp-echo/client/assembly/windows/dev.sh | 27 + .../client/assembly/windows/release.sh | 27 + .../tcp-echo/client/assembly/windows/test.sh | 27 + .../tcp-echo/client/profiles/dev/config.yml | 50 + .../echo/tcp-echo/client/profiles/dev/log.xml | 63 + .../client/profiles/release/config.yml | 50 + .../tcp-echo/client/profiles/release/log.xml | 63 + .../tcp-echo/client/profiles/test/config.yml | 50 + .../tcp-echo/client/profiles/test/log.xml | 63 + examples/echo/tcp-echo/server/app/config.go | 157 + examples/echo/tcp-echo/server/app/echo.go | 152 + examples/echo/tcp-echo/server/app/handler.go | 180 + .../echo/tcp-echo/server/app/readwriter.go | 84 + examples/echo/tcp-echo/server/app/server.go | 191 + .../echo/tcp-echo/server/assembly/bin/load.sh | 186 + .../server/assembly/common/app.properties | 17 + .../tcp-echo/server/assembly/common/build.sh | 77 + .../tcp-echo/server/assembly/linux/dev.sh | 28 + .../tcp-echo/server/assembly/linux/release.sh | 27 + .../tcp-echo/server/assembly/linux/test.sh | 28 + .../echo/tcp-echo/server/assembly/mac/dev.sh | 28 + .../tcp-echo/server/assembly/mac/release.sh | 27 + .../echo/tcp-echo/server/assembly/mac/test.sh | 28 + .../tcp-echo/server/assembly/windows/dev.sh | 27 + .../server/assembly/windows/release.sh | 27 + .../tcp-echo/server/assembly/windows/test.sh | 28 + .../tcp-echo/server/profiles/dev/config.yml | 33 + .../echo/tcp-echo/server/profiles/dev/log.xml | 63 + .../server/profiles/release/config.yml | 33 + .../tcp-echo/server/profiles/release/log.xml | 63 + .../tcp-echo/server/profiles/test/config.yml | 33 + .../tcp-echo/server/profiles/test/log.xml | 63 + examples/echo/udp-echo/change_log.md | 41 + examples/echo/udp-echo/client/app/client.go | 185 + examples/echo/udp-echo/client/app/config.go | 155 + examples/echo/udp-echo/client/app/echo.go | 155 + examples/echo/udp-echo/client/app/handler.go | 99 + examples/echo/udp-echo/client/app/main.go | 235 ++ .../echo/udp-echo/client/app/readwriter.go | 92 + .../echo/udp-echo/client/assembly/bin/load.sh | 186 + .../client/assembly/common/app.properties | 17 + .../udp-echo/client/assembly/common/build.sh | 77 + .../udp-echo/client/assembly/linux/dev.sh | 28 + .../udp-echo/client/assembly/linux/release.sh | 27 + .../udp-echo/client/assembly/linux/test.sh | 28 + .../echo/udp-echo/client/assembly/mac/dev.sh | 28 + .../udp-echo/client/assembly/mac/release.sh | 27 + .../echo/udp-echo/client/assembly/mac/test.sh | 28 + .../udp-echo/client/assembly/windows/dev.sh | 27 + .../client/assembly/windows/release.sh | 27 + .../udp-echo/client/assembly/windows/test.sh | 27 + .../udp-echo/client/profiles/dev/config.toml | 44 + .../echo/udp-echo/client/profiles/dev/log.xml | 63 + .../client/profiles/release/config.toml | 44 + .../udp-echo/client/profiles/release/log.xml | 63 + .../udp-echo/client/profiles/test/config.toml | 44 + .../udp-echo/client/profiles/test/log.xml | 63 + examples/echo/udp-echo/server/app/config.go | 138 + examples/echo/udp-echo/server/app/echo.go | 152 + examples/echo/udp-echo/server/app/handler.go | 199 ++ .../echo/udp-echo/server/app/readwriter.go | 92 + examples/echo/udp-echo/server/app/server.go | 178 + .../echo/udp-echo/server/assembly/bin/load.sh | 186 + .../server/assembly/common/app.properties | 17 + .../udp-echo/server/assembly/common/build.sh | 77 + .../udp-echo/server/assembly/linux/dev.sh | 28 + .../udp-echo/server/assembly/linux/release.sh | 27 + .../udp-echo/server/assembly/linux/test.sh | 28 + .../echo/udp-echo/server/assembly/mac/dev.sh | 28 + .../udp-echo/server/assembly/mac/release.sh | 27 + .../echo/udp-echo/server/assembly/mac/test.sh | 28 + .../udp-echo/server/assembly/windows/dev.sh | 27 + .../server/assembly/windows/release.sh | 27 + .../udp-echo/server/assembly/windows/test.sh | 28 + .../udp-echo/server/profiles/dev/config.toml | 31 + .../echo/udp-echo/server/profiles/dev/log.xml | 63 + .../server/profiles/release/config.toml | 31 + .../udp-echo/server/profiles/release/log.xml | 63 + .../udp-echo/server/profiles/test/config.toml | 31 + .../udp-echo/server/profiles/test/log.xml | 63 + examples/echo/ws-echo/change_log.md | 40 + examples/echo/ws-echo/client/app/client.go | 153 + examples/echo/ws-echo/client/app/config.go | 158 + examples/echo/ws-echo/client/app/echo.go | 146 + examples/echo/ws-echo/client/app/handler.go | 89 + examples/echo/ws-echo/client/app/main.go | 199 ++ .../echo/ws-echo/client/app/readwriter.go | 84 + .../echo/ws-echo/client/assembly/bin/load.sh | 186 + .../client/assembly/common/app.properties | 17 + .../ws-echo/client/assembly/common/build.sh | 77 + .../echo/ws-echo/client/assembly/linux/dev.sh | 28 + .../ws-echo/client/assembly/linux/release.sh | 27 + .../ws-echo/client/assembly/linux/test.sh | 28 + .../echo/ws-echo/client/assembly/mac/dev.sh | 28 + .../ws-echo/client/assembly/mac/release.sh | 27 + .../echo/ws-echo/client/assembly/mac/test.sh | 28 + .../ws-echo/client/assembly/windows/dev.sh | 27 + .../client/assembly/windows/release.sh | 27 + .../ws-echo/client/assembly/windows/test.sh | 27 + .../ws-echo/client/assembly/windows/win32.sh | 29 + .../client/profiles/dev/cert/client.crt | 14 + .../ws-echo/client/profiles/dev/config.toml | 50 + .../echo/ws-echo/client/profiles/dev/log.xml | 63 + .../client/profiles/release/cert/client.crt | 14 + .../client/profiles/release/config.toml | 50 + .../ws-echo/client/profiles/release/log.xml | 63 + .../client/profiles/test/cert/client.crt | 14 + .../ws-echo/client/profiles/test/config.toml | 50 + .../echo/ws-echo/client/profiles/test/log.xml | 63 + examples/echo/ws-echo/js-client/addr.js | 3 + examples/echo/ws-echo/js-client/encoding.js | 3095 +++++++++++++++++ examples/echo/ws-echo/js-client/index.html | 23 + .../ws-echo/js-client/jquery-1.10.2.min.js | 6 + examples/echo/ws-echo/js-client/main.js | 238 ++ examples/echo/ws-echo/js-client/struct.min.js | 22 + examples/echo/ws-echo/js-client/style.css | 104 + examples/echo/ws-echo/server/app/config.go | 151 + examples/echo/ws-echo/server/app/echo.go | 147 + examples/echo/ws-echo/server/app/handler.go | 159 + .../echo/ws-echo/server/app/readwriter.go | 88 + examples/echo/ws-echo/server/app/server.go | 197 ++ .../echo/ws-echo/server/assembly/bin/load.sh | 186 + .../server/assembly/common/app.properties | 17 + .../ws-echo/server/assembly/common/build.sh | 77 + .../echo/ws-echo/server/assembly/linux/dev.sh | 28 + .../ws-echo/server/assembly/linux/release.sh | 27 + .../ws-echo/server/assembly/linux/test.sh | 28 + .../echo/ws-echo/server/assembly/mac/dev.sh | 28 + .../ws-echo/server/assembly/mac/release.sh | 27 + .../echo/ws-echo/server/assembly/mac/test.sh | 28 + .../ws-echo/server/assembly/windows/dev.sh | 27 + .../server/assembly/windows/release.sh | 27 + .../ws-echo/server/assembly/windows/test.sh | 28 + .../ws-echo/server/profiles/dev/config.toml | 37 + .../echo/ws-echo/server/profiles/dev/log.xml | 63 + .../server/profiles/release/config.toml | 37 + .../ws-echo/server/profiles/release/log.xml | 63 + .../ws-echo/server/profiles/test/config.toml | 37 + .../echo/ws-echo/server/profiles/test/log.xml | 63 + examples/echo/wss-echo/change_log.md | 40 + examples/echo/wss-echo/client/app/client.go | 153 + examples/echo/wss-echo/client/app/config.go | 162 + examples/echo/wss-echo/client/app/echo.go | 146 + examples/echo/wss-echo/client/app/handler.go | 89 + examples/echo/wss-echo/client/app/main.go | 206 ++ .../echo/wss-echo/client/app/readwriter.go | 84 + .../echo/wss-echo/client/assembly/bin/load.sh | 186 + .../client/assembly/common/app.properties | 17 + .../wss-echo/client/assembly/common/build.sh | 77 + .../wss-echo/client/assembly/linux/dev.sh | 28 + .../wss-echo/client/assembly/linux/release.sh | 27 + .../wss-echo/client/assembly/linux/test.sh | 28 + .../echo/wss-echo/client/assembly/mac/dev.sh | 28 + .../wss-echo/client/assembly/mac/release.sh | 27 + .../echo/wss-echo/client/assembly/mac/test.sh | 28 + .../wss-echo/client/assembly/windows/dev.sh | 27 + .../client/assembly/windows/release.sh | 27 + .../wss-echo/client/assembly/windows/test.sh | 27 + .../wss-echo/client/assembly/windows/win32.sh | 29 + .../client/profiles/dev/cert/client.crt | 14 + .../wss-echo/client/profiles/dev/config.toml | 54 + .../echo/wss-echo/client/profiles/dev/log.xml | 63 + .../client/profiles/release/cert/client.crt | 14 + .../client/profiles/release/config.toml | 54 + .../wss-echo/client/profiles/release/log.xml | 63 + .../client/profiles/test/cert/client.crt | 14 + .../wss-echo/client/profiles/test/config.toml | 54 + .../wss-echo/client/profiles/test/log.xml | 63 + examples/echo/wss-echo/js-client/addr.js | 3 + examples/echo/wss-echo/js-client/encoding.js | 3095 +++++++++++++++++ examples/echo/wss-echo/js-client/index.html | 23 + .../wss-echo/js-client/jquery-1.10.2.min.js | 6 + examples/echo/wss-echo/js-client/main.js | 246 ++ .../echo/wss-echo/js-client/struct.min.js | 22 + examples/echo/wss-echo/js-client/style.css | 104 + examples/echo/wss-echo/server/app/config.go | 157 + examples/echo/wss-echo/server/app/echo.go | 147 + examples/echo/wss-echo/server/app/handler.go | 159 + .../echo/wss-echo/server/app/readwriter.go | 88 + examples/echo/wss-echo/server/app/server.go | 207 ++ .../echo/wss-echo/server/assembly/bin/load.sh | 186 + .../server/assembly/common/app.properties | 17 + .../wss-echo/server/assembly/common/build.sh | 77 + .../wss-echo/server/assembly/linux/dev.sh | 28 + .../wss-echo/server/assembly/linux/release.sh | 27 + .../wss-echo/server/assembly/linux/test.sh | 28 + .../echo/wss-echo/server/assembly/mac/dev.sh | 28 + .../wss-echo/server/assembly/mac/release.sh | 27 + .../echo/wss-echo/server/assembly/mac/test.sh | 28 + .../wss-echo/server/assembly/windows/dev.sh | 27 + .../server/assembly/windows/release.sh | 27 + .../wss-echo/server/assembly/windows/test.sh | 28 + .../wss-echo/server/profiles/dev/cert/cert.sh | 4 + .../server/profiles/dev/cert/server.crt | 14 + .../server/profiles/dev/cert/server.key | 15 + .../wss-echo/server/profiles/dev/config.toml | 42 + .../echo/wss-echo/server/profiles/dev/log.xml | 63 + .../server/profiles/release/cert/cert.sh | 4 + .../server/profiles/release/cert/server.crt | 14 + .../server/profiles/release/cert/server.key | 15 + .../server/profiles/release/config.toml | 42 + .../wss-echo/server/profiles/release/log.xml | 63 + .../server/profiles/test/cert/cert.sh | 4 + .../server/profiles/test/cert/server.crt | 14 + .../server/profiles/test/cert/server.key | 15 + .../wss-echo/server/profiles/test/config.toml | 42 + .../wss-echo/server/profiles/test/log.xml | 63 + examples/profiles/wss/client_cert/client.crt | 14 + examples/profiles/wss/server_cert/cert.sh | 4 + examples/profiles/wss/server_cert/server.crt | 14 + examples/profiles/wss/server_cert/server.key | 15 + examples/readme.md | 54 + go.mod | 8 + go.sum | 44 + 232 files changed, 20993 insertions(+) create mode 100644 examples/LICENSE create mode 100644 examples/build_all.sh create mode 100644 examples/echo/tcp-echo/change_log.md create mode 100644 examples/echo/tcp-echo/client/app/client.go create mode 100644 examples/echo/tcp-echo/client/app/config.go create mode 100644 examples/echo/tcp-echo/client/app/echo.go create mode 100644 examples/echo/tcp-echo/client/app/handler.go create mode 100644 examples/echo/tcp-echo/client/app/main.go create mode 100644 examples/echo/tcp-echo/client/app/readwriter.go create mode 100644 examples/echo/tcp-echo/client/assembly/bin/load.sh create mode 100644 examples/echo/tcp-echo/client/assembly/common/app.properties create mode 100644 examples/echo/tcp-echo/client/assembly/common/build.sh create mode 100644 examples/echo/tcp-echo/client/assembly/linux/dev.sh create mode 100755 examples/echo/tcp-echo/client/assembly/linux/release.sh create mode 100755 examples/echo/tcp-echo/client/assembly/linux/test.sh create mode 100644 examples/echo/tcp-echo/client/assembly/mac/dev.sh create mode 100755 examples/echo/tcp-echo/client/assembly/mac/release.sh create mode 100644 examples/echo/tcp-echo/client/assembly/mac/test.sh create mode 100644 examples/echo/tcp-echo/client/assembly/windows/dev.sh create mode 100755 examples/echo/tcp-echo/client/assembly/windows/release.sh create mode 100644 examples/echo/tcp-echo/client/assembly/windows/test.sh create mode 100644 examples/echo/tcp-echo/client/profiles/dev/config.yml create mode 100644 examples/echo/tcp-echo/client/profiles/dev/log.xml create mode 100644 examples/echo/tcp-echo/client/profiles/release/config.yml create mode 100644 examples/echo/tcp-echo/client/profiles/release/log.xml create mode 100644 examples/echo/tcp-echo/client/profiles/test/config.yml create mode 100644 examples/echo/tcp-echo/client/profiles/test/log.xml create mode 100644 examples/echo/tcp-echo/server/app/config.go create mode 100644 examples/echo/tcp-echo/server/app/echo.go create mode 100644 examples/echo/tcp-echo/server/app/handler.go create mode 100644 examples/echo/tcp-echo/server/app/readwriter.go create mode 100644 examples/echo/tcp-echo/server/app/server.go create mode 100644 examples/echo/tcp-echo/server/assembly/bin/load.sh create mode 100644 examples/echo/tcp-echo/server/assembly/common/app.properties create mode 100644 examples/echo/tcp-echo/server/assembly/common/build.sh create mode 100644 examples/echo/tcp-echo/server/assembly/linux/dev.sh create mode 100755 examples/echo/tcp-echo/server/assembly/linux/release.sh create mode 100644 examples/echo/tcp-echo/server/assembly/linux/test.sh create mode 100644 examples/echo/tcp-echo/server/assembly/mac/dev.sh create mode 100755 examples/echo/tcp-echo/server/assembly/mac/release.sh create mode 100644 examples/echo/tcp-echo/server/assembly/mac/test.sh create mode 100644 examples/echo/tcp-echo/server/assembly/windows/dev.sh create mode 100755 examples/echo/tcp-echo/server/assembly/windows/release.sh create mode 100644 examples/echo/tcp-echo/server/assembly/windows/test.sh create mode 100644 examples/echo/tcp-echo/server/profiles/dev/config.yml create mode 100644 examples/echo/tcp-echo/server/profiles/dev/log.xml create mode 100644 examples/echo/tcp-echo/server/profiles/release/config.yml create mode 100644 examples/echo/tcp-echo/server/profiles/release/log.xml create mode 100644 examples/echo/tcp-echo/server/profiles/test/config.yml create mode 100644 examples/echo/tcp-echo/server/profiles/test/log.xml create mode 100644 examples/echo/udp-echo/change_log.md create mode 100644 examples/echo/udp-echo/client/app/client.go create mode 100644 examples/echo/udp-echo/client/app/config.go create mode 100644 examples/echo/udp-echo/client/app/echo.go create mode 100644 examples/echo/udp-echo/client/app/handler.go create mode 100644 examples/echo/udp-echo/client/app/main.go create mode 100644 examples/echo/udp-echo/client/app/readwriter.go create mode 100644 examples/echo/udp-echo/client/assembly/bin/load.sh create mode 100644 examples/echo/udp-echo/client/assembly/common/app.properties create mode 100644 examples/echo/udp-echo/client/assembly/common/build.sh create mode 100644 examples/echo/udp-echo/client/assembly/linux/dev.sh create mode 100755 examples/echo/udp-echo/client/assembly/linux/release.sh create mode 100644 examples/echo/udp-echo/client/assembly/linux/test.sh create mode 100644 examples/echo/udp-echo/client/assembly/mac/dev.sh create mode 100755 examples/echo/udp-echo/client/assembly/mac/release.sh create mode 100644 examples/echo/udp-echo/client/assembly/mac/test.sh create mode 100644 examples/echo/udp-echo/client/assembly/windows/dev.sh create mode 100755 examples/echo/udp-echo/client/assembly/windows/release.sh create mode 100644 examples/echo/udp-echo/client/assembly/windows/test.sh create mode 100644 examples/echo/udp-echo/client/profiles/dev/config.toml create mode 100644 examples/echo/udp-echo/client/profiles/dev/log.xml create mode 100644 examples/echo/udp-echo/client/profiles/release/config.toml create mode 100644 examples/echo/udp-echo/client/profiles/release/log.xml create mode 100644 examples/echo/udp-echo/client/profiles/test/config.toml create mode 100644 examples/echo/udp-echo/client/profiles/test/log.xml create mode 100644 examples/echo/udp-echo/server/app/config.go create mode 100644 examples/echo/udp-echo/server/app/echo.go create mode 100644 examples/echo/udp-echo/server/app/handler.go create mode 100644 examples/echo/udp-echo/server/app/readwriter.go create mode 100644 examples/echo/udp-echo/server/app/server.go create mode 100644 examples/echo/udp-echo/server/assembly/bin/load.sh create mode 100644 examples/echo/udp-echo/server/assembly/common/app.properties create mode 100644 examples/echo/udp-echo/server/assembly/common/build.sh create mode 100644 examples/echo/udp-echo/server/assembly/linux/dev.sh create mode 100755 examples/echo/udp-echo/server/assembly/linux/release.sh create mode 100644 examples/echo/udp-echo/server/assembly/linux/test.sh create mode 100644 examples/echo/udp-echo/server/assembly/mac/dev.sh create mode 100755 examples/echo/udp-echo/server/assembly/mac/release.sh create mode 100644 examples/echo/udp-echo/server/assembly/mac/test.sh create mode 100644 examples/echo/udp-echo/server/assembly/windows/dev.sh create mode 100755 examples/echo/udp-echo/server/assembly/windows/release.sh create mode 100644 examples/echo/udp-echo/server/assembly/windows/test.sh create mode 100644 examples/echo/udp-echo/server/profiles/dev/config.toml create mode 100644 examples/echo/udp-echo/server/profiles/dev/log.xml create mode 100644 examples/echo/udp-echo/server/profiles/release/config.toml create mode 100644 examples/echo/udp-echo/server/profiles/release/log.xml create mode 100644 examples/echo/udp-echo/server/profiles/test/config.toml create mode 100644 examples/echo/udp-echo/server/profiles/test/log.xml create mode 100644 examples/echo/ws-echo/change_log.md create mode 100644 examples/echo/ws-echo/client/app/client.go create mode 100644 examples/echo/ws-echo/client/app/config.go create mode 100644 examples/echo/ws-echo/client/app/echo.go create mode 100644 examples/echo/ws-echo/client/app/handler.go create mode 100644 examples/echo/ws-echo/client/app/main.go create mode 100644 examples/echo/ws-echo/client/app/readwriter.go create mode 100644 examples/echo/ws-echo/client/assembly/bin/load.sh create mode 100644 examples/echo/ws-echo/client/assembly/common/app.properties create mode 100644 examples/echo/ws-echo/client/assembly/common/build.sh create mode 100755 examples/echo/ws-echo/client/assembly/linux/dev.sh create mode 100755 examples/echo/ws-echo/client/assembly/linux/release.sh create mode 100755 examples/echo/ws-echo/client/assembly/linux/test.sh create mode 100644 examples/echo/ws-echo/client/assembly/mac/dev.sh create mode 100755 examples/echo/ws-echo/client/assembly/mac/release.sh create mode 100644 examples/echo/ws-echo/client/assembly/mac/test.sh create mode 100644 examples/echo/ws-echo/client/assembly/windows/dev.sh create mode 100755 examples/echo/ws-echo/client/assembly/windows/release.sh create mode 100644 examples/echo/ws-echo/client/assembly/windows/test.sh create mode 100644 examples/echo/ws-echo/client/assembly/windows/win32.sh create mode 100644 examples/echo/ws-echo/client/profiles/dev/cert/client.crt create mode 100644 examples/echo/ws-echo/client/profiles/dev/config.toml create mode 100644 examples/echo/ws-echo/client/profiles/dev/log.xml create mode 100644 examples/echo/ws-echo/client/profiles/release/cert/client.crt create mode 100644 examples/echo/ws-echo/client/profiles/release/config.toml create mode 100644 examples/echo/ws-echo/client/profiles/release/log.xml create mode 100644 examples/echo/ws-echo/client/profiles/test/cert/client.crt create mode 100644 examples/echo/ws-echo/client/profiles/test/config.toml create mode 100644 examples/echo/ws-echo/client/profiles/test/log.xml create mode 100644 examples/echo/ws-echo/js-client/addr.js create mode 100644 examples/echo/ws-echo/js-client/encoding.js create mode 100644 examples/echo/ws-echo/js-client/index.html create mode 100644 examples/echo/ws-echo/js-client/jquery-1.10.2.min.js create mode 100644 examples/echo/ws-echo/js-client/main.js create mode 100644 examples/echo/ws-echo/js-client/struct.min.js create mode 100644 examples/echo/ws-echo/js-client/style.css create mode 100644 examples/echo/ws-echo/server/app/config.go create mode 100644 examples/echo/ws-echo/server/app/echo.go create mode 100644 examples/echo/ws-echo/server/app/handler.go create mode 100644 examples/echo/ws-echo/server/app/readwriter.go create mode 100644 examples/echo/ws-echo/server/app/server.go create mode 100644 examples/echo/ws-echo/server/assembly/bin/load.sh create mode 100644 examples/echo/ws-echo/server/assembly/common/app.properties create mode 100644 examples/echo/ws-echo/server/assembly/common/build.sh create mode 100644 examples/echo/ws-echo/server/assembly/linux/dev.sh create mode 100755 examples/echo/ws-echo/server/assembly/linux/release.sh create mode 100644 examples/echo/ws-echo/server/assembly/linux/test.sh create mode 100644 examples/echo/ws-echo/server/assembly/mac/dev.sh create mode 100755 examples/echo/ws-echo/server/assembly/mac/release.sh create mode 100644 examples/echo/ws-echo/server/assembly/mac/test.sh create mode 100644 examples/echo/ws-echo/server/assembly/windows/dev.sh create mode 100755 examples/echo/ws-echo/server/assembly/windows/release.sh create mode 100644 examples/echo/ws-echo/server/assembly/windows/test.sh create mode 100644 examples/echo/ws-echo/server/profiles/dev/config.toml create mode 100644 examples/echo/ws-echo/server/profiles/dev/log.xml create mode 100644 examples/echo/ws-echo/server/profiles/release/config.toml create mode 100644 examples/echo/ws-echo/server/profiles/release/log.xml create mode 100644 examples/echo/ws-echo/server/profiles/test/config.toml create mode 100644 examples/echo/ws-echo/server/profiles/test/log.xml create mode 100644 examples/echo/wss-echo/change_log.md create mode 100644 examples/echo/wss-echo/client/app/client.go create mode 100644 examples/echo/wss-echo/client/app/config.go create mode 100644 examples/echo/wss-echo/client/app/echo.go create mode 100644 examples/echo/wss-echo/client/app/handler.go create mode 100644 examples/echo/wss-echo/client/app/main.go create mode 100644 examples/echo/wss-echo/client/app/readwriter.go create mode 100644 examples/echo/wss-echo/client/assembly/bin/load.sh create mode 100644 examples/echo/wss-echo/client/assembly/common/app.properties create mode 100644 examples/echo/wss-echo/client/assembly/common/build.sh create mode 100644 examples/echo/wss-echo/client/assembly/linux/dev.sh create mode 100755 examples/echo/wss-echo/client/assembly/linux/release.sh create mode 100755 examples/echo/wss-echo/client/assembly/linux/test.sh create mode 100644 examples/echo/wss-echo/client/assembly/mac/dev.sh create mode 100755 examples/echo/wss-echo/client/assembly/mac/release.sh create mode 100644 examples/echo/wss-echo/client/assembly/mac/test.sh create mode 100644 examples/echo/wss-echo/client/assembly/windows/dev.sh create mode 100755 examples/echo/wss-echo/client/assembly/windows/release.sh create mode 100644 examples/echo/wss-echo/client/assembly/windows/test.sh create mode 100644 examples/echo/wss-echo/client/assembly/windows/win32.sh create mode 100644 examples/echo/wss-echo/client/profiles/dev/cert/client.crt create mode 100644 examples/echo/wss-echo/client/profiles/dev/config.toml create mode 100644 examples/echo/wss-echo/client/profiles/dev/log.xml create mode 100644 examples/echo/wss-echo/client/profiles/release/cert/client.crt create mode 100644 examples/echo/wss-echo/client/profiles/release/config.toml create mode 100644 examples/echo/wss-echo/client/profiles/release/log.xml create mode 100644 examples/echo/wss-echo/client/profiles/test/cert/client.crt create mode 100644 examples/echo/wss-echo/client/profiles/test/config.toml create mode 100644 examples/echo/wss-echo/client/profiles/test/log.xml create mode 100644 examples/echo/wss-echo/js-client/addr.js create mode 100644 examples/echo/wss-echo/js-client/encoding.js create mode 100644 examples/echo/wss-echo/js-client/index.html create mode 100644 examples/echo/wss-echo/js-client/jquery-1.10.2.min.js create mode 100644 examples/echo/wss-echo/js-client/main.js create mode 100644 examples/echo/wss-echo/js-client/struct.min.js create mode 100644 examples/echo/wss-echo/js-client/style.css create mode 100644 examples/echo/wss-echo/server/app/config.go create mode 100644 examples/echo/wss-echo/server/app/echo.go create mode 100644 examples/echo/wss-echo/server/app/handler.go create mode 100644 examples/echo/wss-echo/server/app/readwriter.go create mode 100644 examples/echo/wss-echo/server/app/server.go create mode 100644 examples/echo/wss-echo/server/assembly/bin/load.sh create mode 100644 examples/echo/wss-echo/server/assembly/common/app.properties create mode 100644 examples/echo/wss-echo/server/assembly/common/build.sh create mode 100644 examples/echo/wss-echo/server/assembly/linux/dev.sh create mode 100755 examples/echo/wss-echo/server/assembly/linux/release.sh create mode 100644 examples/echo/wss-echo/server/assembly/linux/test.sh create mode 100644 examples/echo/wss-echo/server/assembly/mac/dev.sh create mode 100755 examples/echo/wss-echo/server/assembly/mac/release.sh create mode 100644 examples/echo/wss-echo/server/assembly/mac/test.sh create mode 100644 examples/echo/wss-echo/server/assembly/windows/dev.sh create mode 100755 examples/echo/wss-echo/server/assembly/windows/release.sh create mode 100644 examples/echo/wss-echo/server/assembly/windows/test.sh create mode 100755 examples/echo/wss-echo/server/profiles/dev/cert/cert.sh create mode 100755 examples/echo/wss-echo/server/profiles/dev/cert/server.crt create mode 100755 examples/echo/wss-echo/server/profiles/dev/cert/server.key create mode 100755 examples/echo/wss-echo/server/profiles/dev/config.toml create mode 100755 examples/echo/wss-echo/server/profiles/dev/log.xml create mode 100755 examples/echo/wss-echo/server/profiles/release/cert/cert.sh create mode 100755 examples/echo/wss-echo/server/profiles/release/cert/server.crt create mode 100755 examples/echo/wss-echo/server/profiles/release/cert/server.key create mode 100755 examples/echo/wss-echo/server/profiles/release/config.toml create mode 100755 examples/echo/wss-echo/server/profiles/release/log.xml create mode 100755 examples/echo/wss-echo/server/profiles/test/cert/cert.sh create mode 100755 examples/echo/wss-echo/server/profiles/test/cert/server.crt create mode 100755 examples/echo/wss-echo/server/profiles/test/cert/server.key create mode 100755 examples/echo/wss-echo/server/profiles/test/config.toml create mode 100755 examples/echo/wss-echo/server/profiles/test/log.xml create mode 100644 examples/profiles/wss/client_cert/client.crt create mode 100755 examples/profiles/wss/server_cert/cert.sh create mode 100755 examples/profiles/wss/server_cert/server.crt create mode 100755 examples/profiles/wss/server_cert/server.key create mode 100644 examples/readme.md diff --git a/examples/LICENSE b/examples/LICENSE new file mode 100644 index 00000000..0c118318 --- /dev/null +++ b/examples/LICENSE @@ -0,0 +1,191 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + Copyright 2016 ~ 2019 Alex Stocks. + + Licensed 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. diff --git a/examples/build_all.sh b/examples/build_all.sh new file mode 100644 index 00000000..e5f757a8 --- /dev/null +++ b/examples/build_all.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash + +cd echo/tcp-echo/client && sh assembly/mac/dev.sh && rm -rf target && cd - +cd echo/tcp-echo/server && sh assembly/mac/dev.sh && rm -rf target && cd - + +cd echo/udp-echo/client && sh assembly/mac/dev.sh && rm -rf target && cd - +cd echo/udp-echo/server && sh assembly/mac/dev.sh && rm -rf target && cd - + +cd echo/ws-echo/client && sh assembly/mac/dev.sh && rm -rf target && cd - +cd echo/ws-echo/server && sh assembly/mac/dev.sh && rm -rf target && cd - + +cd echo/wss-echo/client && sh assembly/mac/dev.sh && rm -rf target && cd - +cd echo/wss-echo/server && sh assembly/mac/dev.sh && rm -rf target && cd - diff --git a/examples/echo/tcp-echo/change_log.md b/examples/echo/tcp-echo/change_log.md new file mode 100644 index 00000000..699c8798 --- /dev/null +++ b/examples/echo/tcp-echo/change_log.md @@ -0,0 +1,37 @@ +# tcp-echo # +--- +*getty tcp examples of Echo Example* + +## LICENSE ## +--- + +> LICENCE : Apache License 2.0 + +## develop history ## +--- + +- 2019/09/05 + > feature + * add writev + +- 2018/06/24 + > improvement + * delete this, using name abbreviation instead. + +- 2018/03/17 + > improvement + * use getty 0.8.2 + +- 2018/03/14 + > improvement + * add keep alive period + * add write package timeout + +- 2018/03/10 + > improvement + * use getty 0.8.1 + +- 2016/11/19 + > 1 add client/app/config.go:GettySessionParam{CompressEncoding} to test compress websocket compression extension. + > + > 2 add server/app/config.go:GettySessionParam{CompressEncoding} to test compress websocket compression extension. diff --git a/examples/echo/tcp-echo/client/app/client.go b/examples/echo/tcp-echo/client/app/client.go new file mode 100644 index 00000000..230b9f2a --- /dev/null +++ b/examples/echo/tcp-echo/client/app/client.go @@ -0,0 +1,172 @@ +/* + * 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. + */ + +package main + +import ( + "math/rand" + "sync" + "sync/atomic" + "time" +) + +import ( + log "github.com/AlexStocks/log4go" + getty "github.com/apache/dubbo-getty" +) + +var ( + reqID uint32 + src = rand.NewSource(time.Now().UnixNano()) +) + +func init() { + rand.Seed(time.Now().UnixNano()) +} + +//////////////////////////////////////////////////////////////////// +// echo client +//////////////////////////////////////////////////////////////////// + +type EchoClient struct { + lock sync.RWMutex + sessions []*clientEchoSession + gettyClient getty.Client +} + +func (c *EchoClient) isAvailable() bool { + if c.selectSession() == nil { + return false + } + + return true +} + +func (c *EchoClient) close() { + c.lock.Lock() + defer c.lock.Unlock() + if c.gettyClient != nil { + c.gettyClient.Close() + c.gettyClient = nil + for _, s := range c.sessions { + log.Info("close client session{%s, last active:%s, request number:%d}", + s.session.Stat(), s.session.GetActive().String(), s.reqNum) + s.session.Close() + } + c.sessions = c.sessions[:0] + } +} + +func (c *EchoClient) selectSession() getty.Session { + // get route server session + c.lock.RLock() + defer c.lock.RUnlock() + count := len(c.sessions) + if count == 0 { + log.Info("client session array is nil...") + return nil + } + + return c.sessions[rand.Int31n(int32(count))].session +} + +func (c *EchoClient) addSession(session getty.Session) { + log.Debug("add session{%s}", session.Stat()) + if session == nil { + return + } + + c.lock.Lock() + c.sessions = append(c.sessions, &clientEchoSession{session: session}) + c.lock.Unlock() +} + +func (c *EchoClient) removeSession(session getty.Session) { + if session == nil { + return + } + + c.lock.Lock() + + for i, s := range c.sessions { + if s.session == session { + c.sessions = append(c.sessions[:i], c.sessions[i+1:]...) + log.Debug("delete session{%s}, its index{%d}", session.Stat(), i) + break + } + } + log.Info("after remove session{%s}, left session number:%d", session.Stat(), len(c.sessions)) + + c.lock.Unlock() +} + +func (c *EchoClient) updateSession(session getty.Session) { + if session == nil { + return + } + + c.lock.Lock() + + for i, s := range c.sessions { + if s.session == session { + c.sessions[i].reqNum++ + break + } + } + + c.lock.Unlock() +} + +func (c *EchoClient) getClientEchoSession(session getty.Session) (clientEchoSession, error) { + var ( + err error + echoSession clientEchoSession + ) + + c.lock.Lock() + + err = errSessionNotExist + for _, s := range c.sessions { + if s.session == session { + echoSession = *s + err = nil + break + } + } + + c.lock.Unlock() + + return echoSession, err +} + +func (c *EchoClient) heartbeat(session getty.Session) { + var pkg EchoPackage + pkg.H.Magic = echoPkgMagic + pkg.H.LogID = (uint32)(src.Int63()) + pkg.H.Sequence = atomic.AddUint32(&reqID, 1) + // pkg.H.ServiceID = 0 + pkg.H.Command = heartbeatCmd + pkg.B = echoHeartbeatRequestString + pkg.H.Len = (uint16)(len(pkg.B) + 1) + + if _, _, err := session.WritePkg(&pkg, WritePkgTimeout); err != nil { + log.Warn("session.WritePkg(session{%s}, pkg{%s}) = error{%v}", session.Stat(), pkg, err) + session.Close() + + c.removeSession(session) + } +} diff --git a/examples/echo/tcp-echo/client/app/config.go b/examples/echo/tcp-echo/client/app/config.go new file mode 100644 index 00000000..e286fdc0 --- /dev/null +++ b/examples/echo/tcp-echo/client/app/config.go @@ -0,0 +1,175 @@ +/* + * 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. + */ + +package main + +import ( + "fmt" + "io/ioutil" + "os" + "path" + "time" +) + +import ( + log "github.com/AlexStocks/log4go" + yaml "gopkg.in/yaml.v2" +) + +const ( + APP_CONF_FILE = "APP_CONF_FILE" + APP_LOG_CONF_FILE = "APP_LOG_CONF_FILE" +) + +var conf *Config + +type ( + GettySessionParam struct { + CompressEncoding bool `default:"false" yaml:"compress_encoding" json:"compress_encoding,omitempty"` + TcpNoDelay bool `default:"true" yaml:"tcp_no_delay" json:"tcp_no_delay,omitempty"` + TcpKeepAlive bool `default:"true" yaml:"tcp_keep_alive" json:"tcp_keep_alive,omitempty"` + KeepAlivePeriod string `default:"180s" yaml:"keep_alive_period" json:"keep_alive_period,omitempty"` + keepAlivePeriod time.Duration + TcpRBufSize int `default:"262144" yaml:"tcp_r_buf_size" json:"tcp_r_buf_size,omitempty"` + TcpWBufSize int `default:"65536" yaml:"tcp_w_buf_size" json:"tcp_w_buf_size,omitempty"` + PkgWQSize int `default:"1024" yaml:"pkg_wq_size" json:"pkg_wq_size,omitempty"` + TcpReadTimeout string `default:"1s" yaml:"tcp_read_timeout" json:"tcp_read_timeout,omitempty"` + tcpReadTimeout time.Duration + TcpWriteTimeout string `default:"5s" yaml:"tcp_write_timeout" json:"tcp_write_timeout,omitempty"` + tcpWriteTimeout time.Duration + WaitTimeout string `default:"7s" yaml:"wait_timeout" json:"wait_timeout,omitempty"` + waitTimeout time.Duration + MaxMsgLen int `default:"1024" yaml:"max_msg_len" json:"max_msg_len,omitempty"` + SessionName string `default:"echo-client" yaml:"session_name" json:"session_name,omitempty"` + } + + // Config holds supported types by the multiconfig package + Config struct { + // local + AppName string `default:"echo-client" yaml:"app_name" json:"app_name,omitempty"` + LocalHost string `default:"127.0.0.1" yaml:"local_host" json:"local_host,omitempty"` + + // server + ServerHost string `default:"127.0.0.1" yaml:"server_host" json:"server_host,omitempty"` + ServerPort int `default:"10000" yaml:"server_port" json:"server_port,omitempty"` + ProfilePort int `default:"10086" yaml:"profile_port" json:"profile_port,omitempty"` + + // session pool + ConnectionNum int `default:"16" yaml:"connection_number" json:"connection_number,omitempty"` + + // heartbeat + HeartbeatPeriod string `default:"15s" yaml:"heartbeat_period" json:"heartbeat_period,omitempty"` + heartbeatPeriod time.Duration + + // session + SessionTimeout string `default:"60s" yaml:"session_timeout" json:"session_timeout,omitempty"` + sessionTimeout time.Duration + + // echo + EchoString string `default:"hello" yaml:"echo_string" json:"echo_string,omitempty"` + EchoTimes int `default:"10" yaml:"echo_times" json:"echo_times,omitempty"` + + // app + FailFastTimeout string `default:"5s" yaml:"fail_fast_timeout" json:"fail_fast_timeout,omitempty"` + failFastTimeout time.Duration + + // session tcp parameters + GettySessionParam GettySessionParam `required:"true" yaml:"getty_session_param" json:"getty_session_param,omitempty"` + } +) + +func initConf() { + var ( + err error + confFile string + ) + + // configure + confFile = os.Getenv(APP_CONF_FILE) + if confFile == "" { + panic(fmt.Sprintf("application configure file name is nil")) + return // I know it is of no usage. Just Err Protection. + } + if path.Ext(confFile) != ".yml" { + panic(fmt.Sprintf("application configure file name{%v} suffix must be .yml", confFile)) + return + } + + conf = &Config{} + confFileStream, err := ioutil.ReadFile(confFile) + if err != nil { + panic(fmt.Sprintf("ioutil.ReadFile(file:%s) = error:%s", confFile, err)) + return + } + err = yaml.Unmarshal(confFileStream, conf) + if err != nil { + panic(fmt.Sprintf("yaml.Unmarshal() = error:%s", err)) + return + } + + conf.heartbeatPeriod, err = time.ParseDuration(conf.HeartbeatPeriod) + if err != nil { + panic(fmt.Sprintf("time.ParseDuration(HeartbeatPeriod{%#v}) = error{%v}", conf.HeartbeatPeriod, err)) + return + } + conf.sessionTimeout, err = time.ParseDuration(conf.SessionTimeout) + if err != nil { + panic(fmt.Sprintf("time.ParseDuration(SessionTimeout{%#v}) = error{%v}", conf.SessionTimeout, err)) + return + } + conf.failFastTimeout, err = time.ParseDuration(conf.FailFastTimeout) + if err != nil { + panic(fmt.Sprintf("time.ParseDuration(FailFastTimeout{%#v}) = error{%v}", conf.FailFastTimeout, err)) + return + } + conf.GettySessionParam.keepAlivePeriod, err = time.ParseDuration(conf.GettySessionParam.KeepAlivePeriod) + if err != nil { + panic(fmt.Sprintf("time.ParseDuration(KeepAlivePeriod{%#v}) = error{%v}", conf.GettySessionParam.KeepAlivePeriod, err)) + return + } + conf.GettySessionParam.tcpReadTimeout, err = time.ParseDuration(conf.GettySessionParam.TcpReadTimeout) + if err != nil { + panic(fmt.Sprintf("time.ParseDuration(TcpReadTimeout{%#v}) = error{%v}", conf.GettySessionParam.TcpReadTimeout, err)) + return + } + conf.GettySessionParam.tcpWriteTimeout, err = time.ParseDuration(conf.GettySessionParam.TcpWriteTimeout) + if err != nil { + panic(fmt.Sprintf("time.ParseDuration(TcpWriteTimeout{%#v}) = error{%v}", conf.GettySessionParam.TcpWriteTimeout, err)) + return + } + conf.GettySessionParam.waitTimeout, err = time.ParseDuration(conf.GettySessionParam.WaitTimeout) + if err != nil { + panic(fmt.Sprintf("time.ParseDuration(WaitTimeout{%#v}) = error{%v}", conf.GettySessionParam.WaitTimeout, err)) + return + } + // gxlog.CInfo("config{%#v}\n", conf) + + // log + confFile = os.Getenv(APP_LOG_CONF_FILE) + if confFile == "" { + panic(fmt.Sprintf("log configure file name is nil")) + return + } + if path.Ext(confFile) != ".xml" { + panic(fmt.Sprintf("log configure file name{%v} suffix must be .xml", confFile)) + return + } + log.LoadConfiguration(confFile) + log.Info("config{%#v}", conf) + + return +} diff --git a/examples/echo/tcp-echo/client/app/echo.go b/examples/echo/tcp-echo/client/app/echo.go new file mode 100644 index 00000000..013740ec --- /dev/null +++ b/examples/echo/tcp-echo/client/app/echo.go @@ -0,0 +1,151 @@ +/* + * 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. + */ + +package main + +import ( + "bytes" + "encoding/binary" + "errors" + "fmt" + "unsafe" +) + +import ( + log "github.com/AlexStocks/log4go" +) + +//////////////////////////////////////////// +// echo command +//////////////////////////////////////////// + +type echoCommand uint32 + +const ( + heartbeatCmd = iota + echoCmd +) + +var echoCommandStrings = [...]string{ + "heartbeat", + "echo", +} + +func (c echoCommand) String() string { + return echoCommandStrings[c] +} + +//////////////////////////////////////////// +// EchoPkgHandler +//////////////////////////////////////////// + +const ( + echoPkgMagic = 0x20160905 + maxEchoStringLen = 0xff + + echoHeartbeatRequestString = "ping" + echoHeartbeatResponseString = "pong" +) + +var ( + ErrNotEnoughStream = errors.New("packet stream is not enough") + ErrTooLargePackage = errors.New("package length is exceed the echo package's legal maximum length.") + ErrIllegalMagic = errors.New("package magic is not right.") +) + +var echoPkgHeaderLen int + +func init() { + echoPkgHeaderLen = (int)((uint)(unsafe.Sizeof(EchoPkgHeader{}))) +} + +type EchoPkgHeader struct { + Magic uint32 + LogID uint32 // log id + + Sequence uint32 // request/response sequence + ServiceID uint32 // service id + + Command uint32 // operation command code + Code int32 // error code + + Len uint16 // body length + _ uint16 + _ int32 // reserved, maybe used as package md5 checksum +} + +type EchoPackage struct { + H EchoPkgHeader + B string +} + +func (p EchoPackage) String() string { + return fmt.Sprintf("log id:%d, sequence:%d, command:%s, echo string:%s", + p.H.LogID, p.H.Sequence, (echoCommand(p.H.Command)).String(), p.B) +} + +func (p EchoPackage) Marshal() (*bytes.Buffer, error) { + var ( + err error + buf *bytes.Buffer + ) + + buf = &bytes.Buffer{} + err = binary.Write(buf, binary.LittleEndian, p.H) + if err != nil { + return nil, err + } + buf.WriteByte((byte)(len(p.B))) + buf.WriteString(p.B) + + return buf, nil +} + +func (p *EchoPackage) Unmarshal(buf *bytes.Buffer) (int, error) { + var ( + err error + len byte + ) + + if buf.Len() < echoPkgHeaderLen { + return 0, ErrNotEnoughStream + } + + // header + err = binary.Read(buf, binary.LittleEndian, &(p.H)) + if err != nil { + return 0, err + } + if p.H.Magic != echoPkgMagic { + log.Error("@p.H.Magic{%x}, right magic{%x}", p.H.Magic, echoPkgMagic) + return 0, ErrIllegalMagic + } + if buf.Len() < (int)(p.H.Len) { + return 0, ErrNotEnoughStream + } + if maxEchoStringLen < p.H.Len-1 { + return 0, ErrTooLargePackage + } + + len, err = buf.ReadByte() + if err != nil { + return 0, nil + } + p.B = (string)(buf.Next((int)(len))) + + return (int)(p.H.Len) + echoPkgHeaderLen, nil +} diff --git a/examples/echo/tcp-echo/client/app/handler.go b/examples/echo/tcp-echo/client/app/handler.go new file mode 100644 index 00000000..1e172bfc --- /dev/null +++ b/examples/echo/tcp-echo/client/app/handler.go @@ -0,0 +1,91 @@ +/* + * 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. + */ + +package main + +import ( + "errors" + "time" +) + +import ( + log "github.com/AlexStocks/log4go" + getty "github.com/apache/dubbo-getty" +) + +var ( + errSessionNotExist = errors.New("session not exist!") + echoMsgHandler = newEchoMessageHandler() +) + +//////////////////////////////////////////// +// EchoMessageHandler +//////////////////////////////////////////// + +type clientEchoSession struct { + session getty.Session + reqNum int32 +} + +type EchoMessageHandler struct{} + +func newEchoMessageHandler() getty.EventListener { + return &EchoMessageHandler{} +} + +func (h *EchoMessageHandler) OnOpen(session getty.Session) error { + client.addSession(session) + + return nil +} + +func (h *EchoMessageHandler) OnError(session getty.Session, err error) { + log.Info("session{%s} got error{%v}, will be closed.", session.Stat(), err) + client.removeSession(session) +} + +func (h *EchoMessageHandler) OnClose(session getty.Session) { + log.Info("session{%s} is closing......", session.Stat()) + client.removeSession(session) +} + +func (h *EchoMessageHandler) OnMessage(session getty.Session, pkg interface{}) { + p, ok := pkg.(*EchoPackage) + if !ok { + log.Error("illegal packge{%#v}", pkg) + return + } + + log.Debug("get echo package{%s}", p) + client.updateSession(session) +} + +func (h *EchoMessageHandler) OnCron(session getty.Session) { + clientEchoSession, err := client.getClientEchoSession(session) + if err != nil { + log.Error("client.getClientSession(session{%s}) = error{%#v}", session.Stat(), err) + return + } + if conf.sessionTimeout.Nanoseconds() < time.Since(session.GetActive()).Nanoseconds() { + log.Warn("session{%s} timeout{%s}, reqNum{%d}", + session.Stat(), time.Since(session.GetActive()).String(), clientEchoSession.reqNum) + client.removeSession(session) + return + } + + client.heartbeat(session) +} diff --git a/examples/echo/tcp-echo/client/app/main.go b/examples/echo/tcp-echo/client/app/main.go new file mode 100644 index 00000000..7ba47ea6 --- /dev/null +++ b/examples/echo/tcp-echo/client/app/main.go @@ -0,0 +1,208 @@ +/* + * 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. + */ + +package main + +import ( + // "flag" + "fmt" + "net" + "net/http" + _ "net/http/pprof" + "os" + "os/signal" + + // "strings" + "sync/atomic" + "syscall" + "time" +) + +import ( + gxlog "github.com/AlexStocks/goext/log" + gxnet "github.com/AlexStocks/goext/net" + gxtime "github.com/AlexStocks/goext/time" + log "github.com/AlexStocks/log4go" + getty "github.com/apache/dubbo-getty" + "github.com/dubbogo/gost/sync" +) + +const ( + pprofPath = "/debug/pprof/" +) + +const ( + WritePkgTimeout = 1e8 +) + +var ( + client EchoClient + taskPool gxsync.GenericTaskPool +) + +//////////////////////////////////////////////////////////////////// +// main +//////////////////////////////////////////////////////////////////// + +func main() { + initConf() + + initProfiling() + + taskPool = gxsync.NewTaskPoolSimple(0) + initClient() + gxlog.CInfo("%s starts successfull!", conf.AppName) + log.Info("%s starts successfull!\n", conf.AppName) + + go test() + + initSignal() +} + +func initProfiling() { + var addr string + + addr = gxnet.HostAddress(conf.LocalHost, conf.ProfilePort) + log.Info("App Profiling startup on address{%v}", addr+pprofPath) + go func() { + log.Info(http.ListenAndServe(addr, nil)) + }() +} + +func newSession(session getty.Session) error { + var ( + ok bool + tcpConn *net.TCPConn + ) + + if conf.GettySessionParam.CompressEncoding { + session.SetCompressType(getty.CompressZip) + } + + if tcpConn, ok = session.Conn().(*net.TCPConn); !ok { + panic(fmt.Sprintf("%s, session.conn{%#v} is not tcp connection\n", session.Stat(), session.Conn())) + } + + tcpConn.SetNoDelay(conf.GettySessionParam.TcpNoDelay) + tcpConn.SetKeepAlive(conf.GettySessionParam.TcpKeepAlive) + if conf.GettySessionParam.TcpKeepAlive { + tcpConn.SetKeepAlivePeriod(conf.GettySessionParam.keepAlivePeriod) + } + tcpConn.SetReadBuffer(conf.GettySessionParam.TcpRBufSize) + tcpConn.SetWriteBuffer(conf.GettySessionParam.TcpWBufSize) + + session.SetName(conf.GettySessionParam.SessionName) + session.SetMaxMsgLen(conf.GettySessionParam.MaxMsgLen) + session.SetPkgHandler(echoPkgHandler) + session.SetEventListener(echoMsgHandler) + session.SetReadTimeout(conf.GettySessionParam.tcpReadTimeout) + session.SetWriteTimeout(conf.GettySessionParam.tcpWriteTimeout) + session.SetCronPeriod((int)(conf.heartbeatPeriod.Nanoseconds() / 1e6)) + session.SetWaitTime(conf.GettySessionParam.waitTimeout) + log.Debug("client new session:%s\n", session.Stat()) + + return nil +} + +func initClient() { + clientOpts := []getty.ClientOption{getty.WithServerAddress(gxnet.HostAddress(conf.ServerHost, conf.ServerPort))} + clientOpts = append(clientOpts, getty.WithClientTaskPool(taskPool)) + + if conf.ConnectionNum != 0 { + clientOpts = append(clientOpts, getty.WithConnectionNumber(conf.ConnectionNum)) + } + + client.gettyClient = getty.NewTCPClient(clientOpts...) + client.gettyClient.RunEventLoop(newSession) +} + +func uninitClient() { + client.close() + if taskPool != nil { + taskPool.Close() + } +} + +func initSignal() { + // signal.Notify的ch信道是阻塞的(signal.Notify不会阻塞发送信号), 需要设置缓冲 + signals := make(chan os.Signal, 1) + // It is not possible to block SIGKILL or syscall.SIGSTOP + signal.Notify(signals, os.Interrupt, os.Kill, syscall.SIGHUP, syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT) + for { + sig := <-signals + log.Info("get signal %s", sig.String()) + switch sig { + case syscall.SIGHUP: + // reload() + default: + go time.AfterFunc(conf.failFastTimeout, func() { + // log.Warn("app exit now by force...") + // os.Exit(1) + log.Exit("app exit now by force...") + log.Close() + }) + + // 要么fastFailTimeout时间内执行完毕下面的逻辑然后程序退出,要么执行上面的超时函数程序强行退出 + uninitClient() + // fmt.Println("app exit now...") + log.Exit("app exit now...") + log.Close() + return + } + } +} + +func echo() { + var pkg EchoPackage + pkg.H.Magic = echoPkgMagic + pkg.H.LogID = (uint32)(src.Int63()) + pkg.H.Sequence = atomic.AddUint32(&reqID, 1) + // pkg.H.ServiceID = 0 + pkg.H.Command = echoCmd + pkg.B = conf.EchoString + pkg.H.Len = (uint16)(len(pkg.B)) + 1 + + if session := client.selectSession(); session != nil { + _, _, err := session.WritePkg(&pkg, WritePkgTimeout) + if err != nil { + log.Warn("session.WritePkg(session{%s}, pkg{%s}) = error{%v}", session.Stat(), pkg, err) + session.Close() + client.removeSession(session) + } + } +} + +func test() { + for { + if client.isAvailable() { + break + } + time.Sleep(1e6) + } + + var ( + cost int64 + counter gxtime.CountWatch + ) + counter.Start() + for i := 0; i < conf.EchoTimes; i++ { + echo() + } + cost = counter.Count() + log.Info("after loop %d times, echo cost %d ms", conf.EchoTimes, cost/1e6) + gxlog.CInfo("after loop %d times, echo cost %d ms", conf.EchoTimes, cost/1e6) +} diff --git a/examples/echo/tcp-echo/client/app/readwriter.go b/examples/echo/tcp-echo/client/app/readwriter.go new file mode 100644 index 00000000..3f2772e1 --- /dev/null +++ b/examples/echo/tcp-echo/client/app/readwriter.go @@ -0,0 +1,82 @@ +/* + * 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. + */ + +package main + +import ( + "bytes" + "errors" + "time" +) + +import ( + log "github.com/AlexStocks/log4go" + getty "github.com/apache/dubbo-getty" +) + +var echoPkgHandler = NewEchoPackageHandler() + +type EchoPackageHandler struct{} + +func NewEchoPackageHandler() getty.ReadWriter { + return &EchoPackageHandler{} +} + +func (h *EchoPackageHandler) Read(ss getty.Session, data []byte) (interface{}, int, error) { + var ( + err error + len int + pkg EchoPackage + buf *bytes.Buffer + ) + + buf = bytes.NewBuffer(data) + len, err = pkg.Unmarshal(buf) + if err != nil { + if err == ErrNotEnoughStream { + return nil, 0, nil + } + + return nil, 0, err + } + + return &pkg, len, nil +} + +func (h *EchoPackageHandler) Write(ss getty.Session, pkg interface{}) ([]byte, error) { + var ( + ok bool + err error + startTime time.Time + echoPkg *EchoPackage + buf *bytes.Buffer + ) + + startTime = time.Now() + if echoPkg, ok = pkg.(*EchoPackage); !ok { + log.Error("illegal pkg:%+v\n", pkg) + return nil, errors.New("invalid echo package!") + } + + buf, err = echoPkg.Marshal() + if err != nil { + log.Warn("binary.Write(echoPkg{%#v}) = err{%#v}", echoPkg, err) + return nil, err + } + log.Debug("WriteEchoPkgTimeMs = %s", time.Since(startTime).String()) + return buf.Bytes(), nil +} diff --git a/examples/echo/tcp-echo/client/assembly/bin/load.sh b/examples/echo/tcp-echo/client/assembly/bin/load.sh new file mode 100644 index 00000000..1d038cc0 --- /dev/null +++ b/examples/echo/tcp-echo/client/assembly/bin/load.sh @@ -0,0 +1,186 @@ +#!/usr/bin/env bash +# ****************************************************** +# DESC : getty app devops script +# AUTHOR : Alex Stocks +# VERSION : 1.0 +# LICENCE : LGPL V3 +# EMAIL : alexstocks@foxmail.com +# MOD : 2016-05-13 02:01 +# FILE : load.sh +# ****************************************************** + +APP_NAME="APPLICATION_NAME" +APP_ARGS="" +SLEEP_INTERVAL=5 +MAX_LIFETIME=4000 + +PROJECT_HOME="" +OS_NAME=`uname` +if [[ ${OS_NAME} != "Windows" ]]; then + PROJECT_HOME=`pwd` + PROJECT_HOME=${PROJECT_HOME}"/" +else + APP_NAME="APPLICATION_NAME.exe" +fi + +export APP_CONF_FILE=${PROJECT_HOME}"TARGET_CONF_FILE" +export APP_LOG_CONF_FILE=${PROJECT_HOME}"TARGET_LOG_CONF_FILE" +# export GOTRACEBACK=system +# export GODEBUG=gctrace=1 + +usage() { + echo "Usage: $0 start" + echo " $0 stop" + echo " $0 term" + echo " $0 restart" + echo " $0 list" + echo " $0 monitor" + echo " $0 crontab" + exit +} + +start() { + APP_LOG_PATH=${PROJECT_HOME}"logs/" + mkdir -p ${APP_LOG_PATH} + APP_BIN=${PROJECT_HOME}sbin/${APP_NAME} + chmod u+x ${APP_BIN} + # CMD="nohup ${APP_BIN} ${APP_ARGS} >>${APP_NAME}.nohup.out 2>&1 &" + CMD="${APP_BIN}" + eval ${CMD} + PID=`ps aux | grep -w ${APP_NAME} | grep -v grep | awk '{print $2}'` + if [[ ${OS_NAME} != "Linux" && ${OS_NAME} != "Darwin" ]]; then + PID=`ps aux | grep -w ${APP_NAME} | grep -v grep | awk '{print $1}'` + fi + CUR=`date +%FT%T` + if [ "${PID}" != "" ]; then + for p in ${PID} + do + echo "start ${APP_NAME} ( pid =" ${p} ") at " ${CUR} + done + fi +} + +stop() { + PID=`ps aux | grep -w ${APP_NAME} | grep -v grep | awk '{print $2}'` + if [[ ${OS_NAME} != "Linux" && ${OS_NAME} != "Darwin" ]]; then + PID=`ps aux | grep -w ${APP_NAME} | grep -v grep | awk '{print $1}'` + fi + if [ "${PID}" != "" ]; + then + for ps in ${PID} + do + echo "kill -SIGINT ${APP_NAME} ( pid =" ${ps} ")" + kill -2 ${ps} + done + fi +} + + +term() { + PID=`ps aux | grep -w ${APP_NAME} | grep -v grep | awk '{print $2}'` + if [[ ${OS_NAME} != "Linux" && ${OS_NAME} != "Darwin" ]]; then + PID=`ps aux | grep -w ${APP_NAME} | grep -v grep | awk '{print $1}'` + fi + if [ "${PID}" != "" ]; + then + for ps in ${PID} + do + echo "kill -9 ${APP_NAME} ( pid =" ${ps} ")" + kill -9 ${ps} + done + fi +} + +list() { + PID=`ps aux | grep -w ${APP_NAME} | grep -v grep | awk '{printf("%s,%s,%s,%s\n", $1, $2, $9, $10)}'` + if [[ ${OS_NAME} != "Linux" && ${OS_NAME} != "Darwin" ]]; then + PID=`ps aux | grep -w ${APP_NAME} | grep -v grep | awk '{printf("%s,%s,%s,%s,%s\n", $1, $4, $6, $7, $8)}'` + fi + + if [ "${PID}" != "" ]; then + echo "list ${APP_NAME}" + + if [[ ${OS_NAME} == "Linux" || ${OS_NAME} == "Darwin" ]]; then + echo "index: user, pid, start, duration" + else + echo "index: PID, WINPID, UID, STIME, COMMAND" + fi + idx=0 + for ps in ${PID} + do + echo "${idx}: ${ps}" + ((idx ++)) + done + fi +} + +monitor() { + idx=0 + while true; do + PID=`ps aux | grep -w ${APP_NAME} | grep -v grep | awk '{print $2}'` + if [[ ${OS_NAME} != "Linux" && ${OS_NAME} != "Darwin" ]]; then + PID=`ps aux | grep -w ${APP_NAME} | grep -v grep | awk '{print $1}'` + fi + if [[ "${PID}" == "" ]]; then + start + idx=0 + fi + + ((LIFE=idx*${SLEEP_INTERVAL})) + echo "${APP_NAME} ( pid = " ${PID} ") has been working in normal state for " $LIFE " seconds." + ((idx ++)) + sleep ${SLEEP_INTERVAL} + done +} + +crontab() { + idx=0 + while true; do + PID=`ps aux | grep -w ${APP_NAME} | grep -v grep | awk '{print $2}'` + if [[ ${OS_NAME} != "Linux" && ${OS_NAME} != "Darwin" ]]; then + PID=`ps aux | grep -w ${APP_NAME} | grep -v grep | awk '{print $1}'` + fi + if [[ "${PID}" == "" ]]; then + start + idx=0 + fi + + ((LIFE=idx*${SLEEP_INTERVAL})) + echo "${APP_NAME} ( pid = " ${PID} ") has been working in normal state for " $LIFE " seconds." + ((idx ++)) + sleep ${SLEEP_INTERVAL} + if [[ ${LIFE} -gt ${MAX_LIFETIME} ]]; then + kill -9 ${PID} + fi + done +} + +opt=$1 +case C"$opt" in + Cstart) + start + ;; + Cstop) + stop + ;; + Cterm) + term + ;; + Crestart) + term + start + ;; + Clist) + list + ;; + Cmonitor) + monitor + ;; + Ccrontab) + crontab + ;; + C*) + usage + ;; +esac + diff --git a/examples/echo/tcp-echo/client/assembly/common/app.properties b/examples/echo/tcp-echo/client/assembly/common/app.properties new file mode 100644 index 00000000..d1f747bc --- /dev/null +++ b/examples/echo/tcp-echo/client/assembly/common/app.properties @@ -0,0 +1,17 @@ +# getty application configure script +# ****************************************************** +# DESC : application environment variable +# AUTHOR : Alex Stocks +# VERSION : 1.0 +# LICENCE : Apache License 2.0 +# EMAIL : alexstocks@foxmail.com +# MOD : 2016-07-12 16:29 +# FILE : app.properties +# ****************************************************** + +export TARGET_EXEC_NAME="echo_client" +export BUILD_PACKAGE="app" + +export TARGET_CONF_FILE="conf/config.yml" +export TARGET_LOG_CONF_FILE="conf/log.xml" + diff --git a/examples/echo/tcp-echo/client/assembly/common/build.sh b/examples/echo/tcp-echo/client/assembly/common/build.sh new file mode 100644 index 00000000..00763725 --- /dev/null +++ b/examples/echo/tcp-echo/client/assembly/common/build.sh @@ -0,0 +1,77 @@ +#!/usr/bin/env bash +# ****************************************************** +# DESC : build script +# AUTHOR : Alex Stocks +# VERSION : 1.0 +# LICENCE : Apache License 2.0 +# EMAIL : alexstocks@foxmail.com +# MOD : 2016-07-12 16:28 +# FILE : build.sh +# ****************************************************** + +rm -rf target/ + +PROJECT_HOME=`pwd` +TARGET_FOLDER=${PROJECT_HOME}/target/${GOOS} + +TARGET_SBIN_NAME=${TARGET_EXEC_NAME} +version=`cat app/version.go | grep Version | awk -F '=' '{print $2}' | awk -F '"' '{print $2}'` +if [[ ${GOOS} == "windows" ]]; then + TARGET_SBIN_NAME=${TARGET_SBIN_NAME}.exe +fi +TARGET_NAME=${TARGET_FOLDER}/${TARGET_SBIN_NAME} +if [[ $PROFILE == "dev" || $PROFILE == "test" ]]; then + # GFLAGS=-gcflags "-N -l" -race -x -v # -x会把go build的详细过程输出 + # GFLAGS=-gcflags "-N -l" -race -v + # GFLAGS="-gcflags \"-N -l\" -v" + cd ${BUILD_PACKAGE} && GOOS=$GOOS GOARCH=$GOARCH go build -gcflags "-N -l" -x -v -i -o ${TARGET_NAME} && cd - +else + # -s去掉符号表(然后panic时候的stack trace就没有任何文件名/行号信息了,这个等价于普通C/C++程序被strip的效果), + # -w去掉DWARF调试信息,得到的程序就不能用gdb调试了。-s和-w也可以分开使用,一般来说如果不打算用gdb调试, + # -w基本没啥损失。-s的损失就有点大了。 + cd ${BUILD_PACKAGE} && GOOS=$GOOS GOARCH=$GOARCH go build -ldflags "-w" -x -v -i -o ${TARGET_NAME} && cd - +fi + +TAR_NAME=${TARGET_EXEC_NAME}-${version}-`date "+%Y%m%d-%H%M"`-${PROFILE} + +mkdir -p ${TARGET_FOLDER}/${TAR_NAME} + +SBIN_DIR=${TARGET_FOLDER}/${TAR_NAME}/sbin +BIN_DIR=${TARGET_FOLDER}/${TAR_NAME} +CONF_DIR=${TARGET_FOLDER}/${TAR_NAME}/conf + +mkdir -p ${SBIN_DIR} +mkdir -p ${CONF_DIR} + +mv ${TARGET_NAME} ${SBIN_DIR} +cp -r assembly/bin ${BIN_DIR} +cd ${BIN_DIR}/bin/ && mv load.sh load_${TARGET_EXEC_NAME}.sh && cd - + +platform=$(uname) +# modify APPLICATION_NAME +if [ ${platform} == "Darwin" ]; then + sed -i "" "s~APPLICATION_NAME~${TARGET_EXEC_NAME}~g" ${BIN_DIR}/bin/* +else + sed -i "s~APPLICATION_NAME~${TARGET_EXEC_NAME}~g" ${BIN_DIR}/bin/* +fi + +# modify TARGET_CONF_FILE +if [ ${platform} == "Darwin" ]; then + sed -i "" "s~TARGET_CONF_FILE~${TARGET_CONF_FILE}~g" ${BIN_DIR}/bin/* +else + sed -i "s~TARGET_CONF_FILE~${TARGET_CONF_FILE}~g" ${BIN_DIR}/bin/* +fi + +# modify TARGET_LOG_CONF_FILE +if [ ${platform} == "Darwin" ]; then + sed -i "" "s~TARGET_LOG_CONF_FILE~${TARGET_LOG_CONF_FILE}~g" ${BIN_DIR}/bin/* +else + sed -i "s~TARGET_LOG_CONF_FILE~${TARGET_LOG_CONF_FILE}~g" ${BIN_DIR}/bin/* +fi + +cp -r profiles/${PROFILE}/* ${CONF_DIR} + +cd ${TARGET_FOLDER} + +tar czf ${TAR_NAME}.tar.gz ${TAR_NAME}/* + diff --git a/examples/echo/tcp-echo/client/assembly/linux/dev.sh b/examples/echo/tcp-echo/client/assembly/linux/dev.sh new file mode 100644 index 00000000..62c82fe6 --- /dev/null +++ b/examples/echo/tcp-echo/client/assembly/linux/dev.sh @@ -0,0 +1,28 @@ +#!/usr/bin/env bash +# ****************************************************** +# DESC : build script for dev env +# AUTHOR : Alex Stocks +# VERSION : 1.0 +# LICENCE : Apache License 2.0 +# EMAIL : alexstocks@foxmail.com +# MOD : 2016-07-12 16:34 +# FILE : dev.sh +# ****************************************************** + + +set -e + +export GOOS=linux +export GOARCH=amd64 + +export PROFILE="dev" +export PROJECT_HOME=`pwd` + +if [ -f "${PROJECT_HOME}/assembly/common/app.properties" ]; then + . ${PROJECT_HOME}/assembly/common/app.properties +fi + + +if [ -f "${PROJECT_HOME}/assembly/common/build.sh" ]; then + sh ${PROJECT_HOME}/assembly/common/build.sh +fi diff --git a/examples/echo/tcp-echo/client/assembly/linux/release.sh b/examples/echo/tcp-echo/client/assembly/linux/release.sh new file mode 100755 index 00000000..2aeaf6d9 --- /dev/null +++ b/examples/echo/tcp-echo/client/assembly/linux/release.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env bash +# ****************************************************** +# DESC : build script for release env +# AUTHOR : Alex Stocks +# VERSION : 1.0 +# LICENCE : Apache License 2.0 +# EMAIL : alexstocks@foxmail.com +# MOD : 2016-07-12 16:34 +# FILE : test.sh +# ****************************************************** + + +set -e + +export GOOS=linux +export GOARCH=amd64 + +export PROFILE="release" +export PROJECT_HOME=`pwd` + +if [ -f "${PROJECT_HOME}/assembly/common/app.properties" ]; then + . ${PROJECT_HOME}/assembly/common/app.properties +fi + +if [ -f "${PROJECT_HOME}/assembly/common/build.sh" ]; then + sh ${PROJECT_HOME}/assembly/common/build.sh +fi diff --git a/examples/echo/tcp-echo/client/assembly/linux/test.sh b/examples/echo/tcp-echo/client/assembly/linux/test.sh new file mode 100755 index 00000000..1bbbefd1 --- /dev/null +++ b/examples/echo/tcp-echo/client/assembly/linux/test.sh @@ -0,0 +1,28 @@ +#!/usr/bin/env bash +# ****************************************************** +# DESC : build script for test env +# AUTHOR : Alex Stocks +# VERSION : 1.0 +# LICENCE : Apache License 2.0 +# EMAIL : alexstocks@foxmail.com +# MOD : 2016-07-12 16:34 +# FILE : test.sh +# ****************************************************** + + +set -e + +export GOOS=linux +export GOARCH=amd64 + +export PROFILE="test" +export PROJECT_HOME=`pwd` + +if [ -f "${PROJECT_HOME}/assembly/common/app.properties" ]; then + . ${PROJECT_HOME}/assembly/common/app.properties +fi + + +if [ -f "${PROJECT_HOME}/assembly/common/build.sh" ]; then + sh ${PROJECT_HOME}/assembly/common/build.sh +fi diff --git a/examples/echo/tcp-echo/client/assembly/mac/dev.sh b/examples/echo/tcp-echo/client/assembly/mac/dev.sh new file mode 100644 index 00000000..65e46867 --- /dev/null +++ b/examples/echo/tcp-echo/client/assembly/mac/dev.sh @@ -0,0 +1,28 @@ +#!/usr/bin/env bash +# ****************************************************** +# DESC : build script for dev env +# AUTHOR : Alex Stocks +# VERSION : 1.0 +# LICENCE : Apache License 2.0 +# EMAIL : alexstocks@foxmail.com +# MOD : 2016-07-12 16:34 +# FILE : dev.sh +# ****************************************************** + + +set -e + +export GOOS=darwin +export GOARCH=amd64 + +export PROFILE="dev" +export PROJECT_HOME=`pwd` + +if [ -f "${PROJECT_HOME}/assembly/common/app.properties" ]; then + . ${PROJECT_HOME}/assembly/common/app.properties +fi + + +if [ -f "${PROJECT_HOME}/assembly/common/build.sh" ]; then + sh ${PROJECT_HOME}/assembly/common/build.sh +fi diff --git a/examples/echo/tcp-echo/client/assembly/mac/release.sh b/examples/echo/tcp-echo/client/assembly/mac/release.sh new file mode 100755 index 00000000..688288b3 --- /dev/null +++ b/examples/echo/tcp-echo/client/assembly/mac/release.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env bash +# ****************************************************** +# DESC : build script for release env +# AUTHOR : Alex Stocks +# VERSION : 1.0 +# LICENCE : Apache License 2.0 +# EMAIL : alexstocks@foxmail.com +# MOD : 2016-07-12 16:34 +# FILE : test.sh +# ****************************************************** + + +set -e + +export GOOS=darwin +export GOARCH=amd64 + +export PROFILE="release" +export PROJECT_HOME=`pwd` + +if [ -f "${PROJECT_HOME}/assembly/common/app.properties" ]; then + . ${PROJECT_HOME}/assembly/common/app.properties +fi + +if [ -f "${PROJECT_HOME}/assembly/common/build.sh" ]; then + sh ${PROJECT_HOME}/assembly/common/build.sh +fi diff --git a/examples/echo/tcp-echo/client/assembly/mac/test.sh b/examples/echo/tcp-echo/client/assembly/mac/test.sh new file mode 100644 index 00000000..56d6c11e --- /dev/null +++ b/examples/echo/tcp-echo/client/assembly/mac/test.sh @@ -0,0 +1,28 @@ +#!/usr/bin/env bash +# ****************************************************** +# DESC : build script for test env +# AUTHOR : Alex Stocks +# VERSION : 1.0 +# LICENCE : Apache License 2.0 +# EMAIL : alexstocks@foxmail.com +# MOD : 2016-07-12 16:34 +# FILE : test.sh +# ****************************************************** + + +set -e + +export GOOS=darwin +export GOARCH=amd64 + +export PROFILE="test" +export PROJECT_HOME=`pwd` + +if [ -f "${PROJECT_HOME}/assembly/common/app.properties" ]; then + . ${PROJECT_HOME}/assembly/common/app.properties +fi + + +if [ -f "${PROJECT_HOME}/assembly/common/build.sh" ]; then + sh ${PROJECT_HOME}/assembly/common/build.sh +fi diff --git a/examples/echo/tcp-echo/client/assembly/windows/dev.sh b/examples/echo/tcp-echo/client/assembly/windows/dev.sh new file mode 100644 index 00000000..94ed49ea --- /dev/null +++ b/examples/echo/tcp-echo/client/assembly/windows/dev.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env bash +# ****************************************************** +# DESC : build script for dev env +# AUTHOR : Alex Stocks +# VERSION : 1.0 +# LICENCE : Apache License 2.0 +# EMAIL : alexstocks@foxmail.com +# MOD : 2016-07-12 16:34 +# FILE : dev.sh +# ****************************************************** + + +set -e + +export GOOS=windows +export GOARCH=amd64 + +export PROFILE="dev" +export PROJECT_HOME=`pwd` + +if [ -f "${PROJECT_HOME}/assembly/common/app.properties" ]; then + . ${PROJECT_HOME}/assembly/common/app.properties +fi + +if [ -f "${PROJECT_HOME}/assembly/common/build.sh" ]; then + sh ${PROJECT_HOME}/assembly/common/build.sh +fi diff --git a/examples/echo/tcp-echo/client/assembly/windows/release.sh b/examples/echo/tcp-echo/client/assembly/windows/release.sh new file mode 100755 index 00000000..f317720b --- /dev/null +++ b/examples/echo/tcp-echo/client/assembly/windows/release.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env bash +# ****************************************************** +# DESC : build script for release env +# AUTHOR : Alex Stocks +# VERSION : 1.0 +# LICENCE : Apache License 2.0 +# EMAIL : alexstocks@foxmail.com +# MOD : 2016-07-12 16:34 +# FILE : test.sh +# ****************************************************** + + +set -e + +export GOOS=windows +export GOARCH=amd64 + +export PROFILE="release" +export PROJECT_HOME=`pwd` + +if [ -f "${PROJECT_HOME}/assembly/common/app.properties" ]; then + . ${PROJECT_HOME}/assembly/common/app.properties +fi + +if [ -f "${PROJECT_HOME}/assembly/common/build.sh" ]; then + sh ${PROJECT_HOME}/assembly/common/build.sh +fi diff --git a/examples/echo/tcp-echo/client/assembly/windows/test.sh b/examples/echo/tcp-echo/client/assembly/windows/test.sh new file mode 100644 index 00000000..7dd2bec5 --- /dev/null +++ b/examples/echo/tcp-echo/client/assembly/windows/test.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env bash +# ****************************************************** +# DESC : build script for test env +# AUTHOR : Alex Stocks +# VERSION : 1.0 +# LICENCE : Apache License 2.0 +# EMAIL : alexstocks@foxmail.com +# MOD : 2016-07-12 16:34 +# FILE : test.sh +# ****************************************************** + + +set -e + +export GOOS=windows +export GOARCH=amd64 + +export PROFILE="test" +export PROJECT_HOME=`pwd` + +if [ -f "${PROJECT_HOME}/assembly/common/app.properties" ]; then + . ${PROJECT_HOME}/assembly/common/app.properties +fi + +if [ -f "${PROJECT_HOME}/assembly/common/build.sh" ]; then + sh ${PROJECT_HOME}/assembly/common/build.sh +fi diff --git a/examples/echo/tcp-echo/client/profiles/dev/config.yml b/examples/echo/tcp-echo/client/profiles/dev/config.yml new file mode 100644 index 00000000..30709279 --- /dev/null +++ b/examples/echo/tcp-echo/client/profiles/dev/config.yml @@ -0,0 +1,50 @@ +# yaml configure file +app_name : "ECHO-CLIENT" + +# host +local_host : "127.0.0.1" + +# server +server_host : "127.0.0.1" +server_port : 10000 + +profile_port : 20080 + +# connection pool +# 连接池连接数目 +connection_number : 2 + +# session +# client与server之间连接的心跳周期 +heartbeat_period : "10s" +# client与server之间连接的超时时间 +session_timeout : "20s" + +# client +# client echo request string +echo_string : "Hello, getty!" +# 发送echo请求次数 +echo_times : 10000 + +# app fail fast +fail_fast_timeout : "3s" + +# tcp +getty_session_param: + compress_encoding : true + tcp_no_delay : true + tcp_keep_alive : true + keep_alive_period : "120s" + tcp_r_buf_size : 262144 + tcp_w_buf_size : 65536 + pkg_rq_size : 512 + pkg_wq_size : 256 + tcp_read_timeout : "1s" + tcp_write_timeout : "5s" + wait_timeout : "1s" + max_msg_len : 128 + session_name : "echo-client" + +task_queue_length : 1024 +task_queue_number : 20 +task_pool_size : 1000 diff --git a/examples/echo/tcp-echo/client/profiles/dev/log.xml b/examples/echo/tcp-echo/client/profiles/dev/log.xml new file mode 100644 index 00000000..8b84716c --- /dev/null +++ b/examples/echo/tcp-echo/client/profiles/dev/log.xml @@ -0,0 +1,63 @@ + + + stdout + console + + DEBUG + + + debug_file + file + DEBUG + logs/debug.log + [%D %T] [%L] [%S] %M + true + 0M + 0K + true + + + info_file + file + INFO + logs/info.log + + [%D %T] [%L] [%S] %M + true + 0M + 0K + true + + + warn_file + file + WARNING + logs/warn.log + [%D %T] [%L] [%S] %M + true + 0M + 0K + true + + + error_file + file + ERROR + logs/error.log + [%D %T] [%L] [%S] %M + true + 0M + 0K + true + + diff --git a/examples/echo/tcp-echo/client/profiles/release/config.yml b/examples/echo/tcp-echo/client/profiles/release/config.yml new file mode 100644 index 00000000..68d171d9 --- /dev/null +++ b/examples/echo/tcp-echo/client/profiles/release/config.yml @@ -0,0 +1,50 @@ +# yaml configure file +app_name : "ECHO-CLIENT" + +# host +local_host : "127.0.0.1" + +# server +server_host : "127.0.0.1" +server_port : 10000 + +profile_port : 20080 + +# connection pool +# 连接池连接数目 +connection_number : 2 + +# session +# client与server之间连接的心跳周期 +heartbeat_period : "10s" +# client与server之间连接的超时时间 +session_timeout : "20s" + +# client +# client echo request string +echo_string : "Hello, getty!" +# 发送echo请求次数 +echo_times : 10000 + +# app fail fast +fail_fast_timeout : "3s" + +# tcp +getty_session_param: + compress_encoding : true + tcp_no_delay : true + tcp_keep_alive : true + keep_alive_period : "120s" + tcp_r_buf_size : 262144 + tcp_w_buf_size : 65536 + pkg_rq_size : 512 + pkg_wq_size : 256 + tcp_read_timeout : "1s" + tcp_write_timeout : "5s" + wait_timeout : "1s" + max_msg_len : 128 + session_name : "echo-client" + +task_queue_length : 1024 +task_queue_number : 20 +task_pool_size : 1000 \ No newline at end of file diff --git a/examples/echo/tcp-echo/client/profiles/release/log.xml b/examples/echo/tcp-echo/client/profiles/release/log.xml new file mode 100644 index 00000000..8b84716c --- /dev/null +++ b/examples/echo/tcp-echo/client/profiles/release/log.xml @@ -0,0 +1,63 @@ + + + stdout + console + + DEBUG + + + debug_file + file + DEBUG + logs/debug.log + [%D %T] [%L] [%S] %M + true + 0M + 0K + true + + + info_file + file + INFO + logs/info.log + + [%D %T] [%L] [%S] %M + true + 0M + 0K + true + + + warn_file + file + WARNING + logs/warn.log + [%D %T] [%L] [%S] %M + true + 0M + 0K + true + + + error_file + file + ERROR + logs/error.log + [%D %T] [%L] [%S] %M + true + 0M + 0K + true + + diff --git a/examples/echo/tcp-echo/client/profiles/test/config.yml b/examples/echo/tcp-echo/client/profiles/test/config.yml new file mode 100644 index 00000000..68d171d9 --- /dev/null +++ b/examples/echo/tcp-echo/client/profiles/test/config.yml @@ -0,0 +1,50 @@ +# yaml configure file +app_name : "ECHO-CLIENT" + +# host +local_host : "127.0.0.1" + +# server +server_host : "127.0.0.1" +server_port : 10000 + +profile_port : 20080 + +# connection pool +# 连接池连接数目 +connection_number : 2 + +# session +# client与server之间连接的心跳周期 +heartbeat_period : "10s" +# client与server之间连接的超时时间 +session_timeout : "20s" + +# client +# client echo request string +echo_string : "Hello, getty!" +# 发送echo请求次数 +echo_times : 10000 + +# app fail fast +fail_fast_timeout : "3s" + +# tcp +getty_session_param: + compress_encoding : true + tcp_no_delay : true + tcp_keep_alive : true + keep_alive_period : "120s" + tcp_r_buf_size : 262144 + tcp_w_buf_size : 65536 + pkg_rq_size : 512 + pkg_wq_size : 256 + tcp_read_timeout : "1s" + tcp_write_timeout : "5s" + wait_timeout : "1s" + max_msg_len : 128 + session_name : "echo-client" + +task_queue_length : 1024 +task_queue_number : 20 +task_pool_size : 1000 \ No newline at end of file diff --git a/examples/echo/tcp-echo/client/profiles/test/log.xml b/examples/echo/tcp-echo/client/profiles/test/log.xml new file mode 100644 index 00000000..8b84716c --- /dev/null +++ b/examples/echo/tcp-echo/client/profiles/test/log.xml @@ -0,0 +1,63 @@ + + + stdout + console + + DEBUG + + + debug_file + file + DEBUG + logs/debug.log + [%D %T] [%L] [%S] %M + true + 0M + 0K + true + + + info_file + file + INFO + logs/info.log + + [%D %T] [%L] [%S] %M + true + 0M + 0K + true + + + warn_file + file + WARNING + logs/warn.log + [%D %T] [%L] [%S] %M + true + 0M + 0K + true + + + error_file + file + ERROR + logs/error.log + [%D %T] [%L] [%S] %M + true + 0M + 0K + true + + diff --git a/examples/echo/tcp-echo/server/app/config.go b/examples/echo/tcp-echo/server/app/config.go new file mode 100644 index 00000000..23ab4656 --- /dev/null +++ b/examples/echo/tcp-echo/server/app/config.go @@ -0,0 +1,157 @@ +/* + * 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. + */ + +package main + +import ( + "fmt" + "io/ioutil" + "os" + "path" + "time" +) + +import ( + log "github.com/AlexStocks/log4go" + yaml "gopkg.in/yaml.v2" +) + +const ( + APP_CONF_FILE = "APP_CONF_FILE" + APP_LOG_CONF_FILE = "APP_LOG_CONF_FILE" +) + +var conf *Config + +type ( + GettySessionParam struct { + CompressEncoding bool `default:"false" yaml:"compress_encoding" json:"compress_encoding,omitempty"` + TcpNoDelay bool `default:"true" yaml:"tcp_no_delay" json:"tcp_no_delay,omitempty"` + TcpKeepAlive bool `default:"true" yaml:"tcp_keep_alive" json:"tcp_keep_alive,omitempty"` + KeepAlivePeriod string `default:"180s" yaml:"keep_alive_period" json:"keep_alive_period,omitempty"` + keepAlivePeriod time.Duration + TcpRBufSize int `default:"262144" yaml:"tcp_r_buf_size" json:"tcp_r_buf_size,omitempty"` + TcpWBufSize int `default:"65536" yaml:"tcp_w_buf_size" json:"tcp_w_buf_size,omitempty"` + PkgWQSize int `default:"1024" yaml:"pkg_wq_size" json:"pkg_wq_size,omitempty"` + TcpReadTimeout string `default:"1s" yaml:"tcp_read_timeout" json:"tcp_read_timeout,omitempty"` + tcpReadTimeout time.Duration + TcpWriteTimeout string `default:"5s" yaml:"tcp_write_timeout" json:"tcp_write_timeout,omitempty"` + tcpWriteTimeout time.Duration + WaitTimeout string `default:"7s" yaml:"wait_timeout" json:"wait_timeout,omitempty"` + waitTimeout time.Duration + MaxMsgLen int `default:"1024" yaml:"max_msg_len" json:"max_msg_len,omitempty"` + SessionName string `default:"echo-server" yaml:"session_name" json:"session_name,omitempty"` + } + + // Config holds supported types by the multiconfig package + Config struct { + // local address + AppName string `default:"echo-server" yaml:"app_name" json:"app_name,omitempty"` + Host string `default:"127.0.0.1" yaml:"host" json:"host,omitempty"` + Ports []string `yaml:"ports" json:"ports,omitempty"` // `default:["10000"]` + ProfilePort int `default:"10086" yaml:"profile_port" json:"profile_port,omitempty"` + + // session + SessionTimeout string `default:"60s" yaml:"session_timeout" json:"session_timeout,omitempty"` + sessionTimeout time.Duration + SessionNumber int `default:"1000" yaml:"session_number" json:"session_number,omitempty"` + + // app + FailFastTimeout string `default:"5s" yaml:"fail_fast_timeout" json:"fail_fast_timeout,omitempty"` + failFastTimeout time.Duration + + // session tcp parameters + GettySessionParam GettySessionParam `required:"true" yaml:"getty_session_param" json:"getty_session_param,omitempty"` + } +) + +func initConf() { + var ( + err error + confFile string + ) + + // configure + confFile = os.Getenv(APP_CONF_FILE) + if confFile == "" { + panic(fmt.Sprintf("application configure file name is nil")) + return // I know it is of no usage. Just Err Protection. + } + if path.Ext(confFile) != ".yml" { + panic(fmt.Sprintf("application configure file name{%v} suffix must be .yml", confFile)) + return + } + + conf = &Config{} + confFileStream, err := ioutil.ReadFile(confFile) + if err != nil { + panic(fmt.Sprintf("ioutil.ReadFile(file:%s) = error:%s", confFile, err)) + return + } + err = yaml.Unmarshal(confFileStream, conf) + if err != nil { + panic(fmt.Sprintf("yaml.Unmarshal() = error:%s", err)) + return + } + + conf.sessionTimeout, err = time.ParseDuration(conf.SessionTimeout) + if err != nil { + panic(fmt.Sprintf("time.ParseDuration(SessionTimeout{%#v}) = error{%v}", conf.SessionTimeout, err)) + return + } + conf.failFastTimeout, err = time.ParseDuration(conf.FailFastTimeout) + if err != nil { + panic(fmt.Sprintf("time.ParseDuration(FailFastTimeout{%#v}) = error{%v}", conf.FailFastTimeout, err)) + return + } + conf.GettySessionParam.keepAlivePeriod, err = time.ParseDuration(conf.GettySessionParam.KeepAlivePeriod) + if err != nil { + panic(fmt.Sprintf("time.ParseDuration(KeepAlivePeriod{%#v}) = error{%v}", conf.GettySessionParam.KeepAlivePeriod, err)) + return + } + conf.GettySessionParam.tcpReadTimeout, err = time.ParseDuration(conf.GettySessionParam.TcpReadTimeout) + if err != nil { + panic(fmt.Sprintf("time.ParseDuration(TcpReadTimeout{%#v}) = error{%v}", conf.GettySessionParam.TcpReadTimeout, err)) + return + } + conf.GettySessionParam.tcpWriteTimeout, err = time.ParseDuration(conf.GettySessionParam.TcpWriteTimeout) + if err != nil { + panic(fmt.Sprintf("time.ParseDuration(TcpWriteTimeout{%#v}) = error{%v}", conf.GettySessionParam.TcpWriteTimeout, err)) + return + } + conf.GettySessionParam.waitTimeout, err = time.ParseDuration(conf.GettySessionParam.WaitTimeout) + if err != nil { + panic(fmt.Sprintf("time.ParseDuration(WaitTimeout{%#v}) = error{%v}", conf.GettySessionParam.WaitTimeout, err)) + return + } + // gxlog.CInfo("config{%#v}\n", conf) + + // log + confFile = os.Getenv(APP_LOG_CONF_FILE) + if confFile == "" { + panic(fmt.Sprintf("log configure file name is nil")) + return + } + if path.Ext(confFile) != ".xml" { + panic(fmt.Sprintf("log configure file name{%v} suffix must be .xml", confFile)) + return + } + log.LoadConfiguration(confFile) + log.Info("config{%#v}", conf) + + return +} diff --git a/examples/echo/tcp-echo/server/app/echo.go b/examples/echo/tcp-echo/server/app/echo.go new file mode 100644 index 00000000..b7df0c85 --- /dev/null +++ b/examples/echo/tcp-echo/server/app/echo.go @@ -0,0 +1,152 @@ +/* + * 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. + */ + +package main + +import ( + "bytes" + "encoding/binary" + "errors" + "fmt" + "unsafe" +) + +import ( + log "github.com/AlexStocks/log4go" +) + +//////////////////////////////////////////// +// echo command +//////////////////////////////////////////// + +type echoCommand uint32 + +const ( + heartbeatCmd = iota + echoCmd +) + +var echoCommandStrings = [...]string{ + "heartbeat", + "echo", +} + +func (c echoCommand) String() string { + return echoCommandStrings[c] +} + +//////////////////////////////////////////// +// EchoPkgHandler +//////////////////////////////////////////// + +const ( + echoPkgMagic = 0x20160905 + maxEchoStringLen = 0xff + + echoHeartbeatRequestString = "ping" + echoHeartbeatResponseString = "pong" +) + +var ( + ErrNotEnoughStream = errors.New("packet stream is not enough") + ErrTooLargePackage = errors.New("package length is exceed the echo package's legal maximum length.") + ErrIllegalMagic = errors.New("package magic is not right.") +) + +var echoPkgHeaderLen int + +func init() { + echoPkgHeaderLen = (int)((uint)(unsafe.Sizeof(EchoPkgHeader{}))) +} + +type EchoPkgHeader struct { + Magic uint32 + LogID uint32 // log id + + Sequence uint32 // request/response sequence + ServiceID uint32 // service id + + Command uint32 // operation command code + Code int32 // error code + + Len uint16 // body length + _ uint16 + _ int32 // reserved, maybe used as package md5 checksum +} + +type EchoPackage struct { + H EchoPkgHeader + B string +} + +func (p EchoPackage) String() string { + return fmt.Sprintf("log id:%d, sequence:%d, command:%s, echo string:%s", + p.H.LogID, p.H.Sequence, (echoCommand(p.H.Command)).String(), p.B) +} + +func (p EchoPackage) Marshal() (*bytes.Buffer, error) { + var ( + err error + buf *bytes.Buffer + ) + + buf = &bytes.Buffer{} + err = binary.Write(buf, binary.LittleEndian, p.H) + if err != nil { + return nil, err + } + buf.WriteByte((byte)(len(p.B))) + buf.WriteString(p.B) + + return buf, nil +} + +func (p *EchoPackage) Unmarshal(buf *bytes.Buffer) (int, error) { + var ( + err error + len byte + ) + + if buf.Len() < echoPkgHeaderLen { + return 0, ErrNotEnoughStream + } + + // header + err = binary.Read(buf, binary.LittleEndian, &(p.H)) + if err != nil { + return 0, err + } + if p.H.Magic != echoPkgMagic { + log.Error("@p.H.Magic{%x}, right magic{%x}", p.H.Magic, echoPkgMagic) + return 0, ErrIllegalMagic + } + if buf.Len() < (int)(p.H.Len) { + return 0, ErrNotEnoughStream + } + // 防止恶意客户端把这个字段设置过大导致服务端死等或者服务端在准备对应的缓冲区时内存崩溃 + if maxEchoStringLen < p.H.Len-1 { + return 0, ErrTooLargePackage + } + + len, err = buf.ReadByte() + if err != nil { + return 0, nil + } + p.B = (string)(buf.Next((int)(len))) + + return (int)(p.H.Len) + echoPkgHeaderLen, nil +} diff --git a/examples/echo/tcp-echo/server/app/handler.go b/examples/echo/tcp-echo/server/app/handler.go new file mode 100644 index 00000000..7498ba3f --- /dev/null +++ b/examples/echo/tcp-echo/server/app/handler.go @@ -0,0 +1,180 @@ +/* + * 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. + */ + +package main + +import ( + "errors" + "sync" + "time" +) + +import ( + log "github.com/AlexStocks/log4go" + getty "github.com/apache/dubbo-getty" +) + +const ( + WritePkgTimeout = 1e8 +) + +var ( + errTooManySessions = errors.New("Too many echo sessions!") + hbHandler = &HeartbeatHandler{} + msgHandler = &MessageHandler{} + echoMsgHandler = newEchoMessageHandler() +) + +type PackageHandler interface { + Handle(getty.Session, *EchoPackage) error +} + +//////////////////////////////////////////// +// heartbeat handler +//////////////////////////////////////////// + +type HeartbeatHandler struct{} + +func (h *HeartbeatHandler) Handle(session getty.Session, pkg *EchoPackage) error { + log.Debug("get echo heartbeat package{%s}", pkg) + + var rspPkg EchoPackage + rspPkg.H = pkg.H + rspPkg.B = echoHeartbeatResponseString + rspPkg.H.Len = uint16(len(rspPkg.B) + 1) + _, _, err := session.WritePkg(&rspPkg, WritePkgTimeout) + return err +} + +//////////////////////////////////////////// +// message handler +//////////////////////////////////////////// + +type MessageHandler struct{} + +func (h *MessageHandler) Handle(session getty.Session, pkg *EchoPackage) error { + log.Debug("get echo package{%s}", pkg) + // write echo message handle logic here. + _, _, err := session.WritePkg(pkg, WritePkgTimeout) + if err != nil { + log.Warn("session.WritePkg(session{%s}, pkg{%s}) = error{%v}", session.Stat(), pkg, err) + session.Close() + } + return err +} + +//////////////////////////////////////////// +// EchoMessageHandler +//////////////////////////////////////////// + +type clientEchoSession struct { + session getty.Session + reqNum int32 +} + +type EchoMessageHandler struct { + handlers map[uint32]PackageHandler + + rwlock sync.RWMutex + sessionMap map[getty.Session]*clientEchoSession +} + +func newEchoMessageHandler() *EchoMessageHandler { + handlers := make(map[uint32]PackageHandler) + handlers[heartbeatCmd] = hbHandler + handlers[echoCmd] = msgHandler + + return &EchoMessageHandler{sessionMap: make(map[getty.Session]*clientEchoSession), handlers: handlers} +} + +func (h *EchoMessageHandler) OnOpen(session getty.Session) error { + var err error + + h.rwlock.RLock() + if conf.SessionNumber <= len(h.sessionMap) { + err = errTooManySessions + } + h.rwlock.RUnlock() + if err != nil { + return err + } + + log.Info("got session:%s", session.Stat()) + h.rwlock.Lock() + h.sessionMap[session] = &clientEchoSession{session: session} + h.rwlock.Unlock() + return nil +} + +func (h *EchoMessageHandler) OnError(session getty.Session, err error) { + log.Info("session{%s} got error{%v}, will be closed.", session.Stat(), err) + h.rwlock.Lock() + delete(h.sessionMap, session) + h.rwlock.Unlock() +} + +func (h *EchoMessageHandler) OnClose(session getty.Session) { + log.Info("session{%s} is closing......", session.Stat()) + h.rwlock.Lock() + delete(h.sessionMap, session) + h.rwlock.Unlock() +} + +func (h *EchoMessageHandler) OnMessage(session getty.Session, pkg interface{}) { + p, ok := pkg.(*EchoPackage) + if !ok { + log.Error("illegal packge{%#v}", pkg) + return + } + + handler, ok := h.handlers[p.H.Command] + if !ok { + log.Error("illegal command{%d}", p.H.Command) + return + } + err := handler.Handle(session, p) + if err != nil { + h.rwlock.Lock() + if _, ok := h.sessionMap[session]; ok { + h.sessionMap[session].reqNum++ + } + h.rwlock.Unlock() + } +} + +func (h *EchoMessageHandler) OnCron(session getty.Session) { + var ( + flag bool + active time.Time + ) + h.rwlock.RLock() + if _, ok := h.sessionMap[session]; ok { + active = session.GetActive() + if conf.sessionTimeout.Nanoseconds() < time.Since(active).Nanoseconds() { + flag = true + log.Warn("session{%s} timeout{%s}, reqNum{%d}", + session.Stat(), time.Since(active).String(), h.sessionMap[session].reqNum) + } + } + h.rwlock.RUnlock() + if flag { + h.rwlock.Lock() + delete(h.sessionMap, session) + h.rwlock.Unlock() + session.Close() + } +} diff --git a/examples/echo/tcp-echo/server/app/readwriter.go b/examples/echo/tcp-echo/server/app/readwriter.go new file mode 100644 index 00000000..8782757b --- /dev/null +++ b/examples/echo/tcp-echo/server/app/readwriter.go @@ -0,0 +1,84 @@ +/* + * 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. + */ + +package main + +import ( + "bytes" + "errors" + "time" +) + +import ( + log "github.com/AlexStocks/log4go" + getty "github.com/apache/dubbo-getty" +) + +var echoPkgHandler = NewEchoPackageHandler() + +type EchoPackageHandler struct{} + +func NewEchoPackageHandler() *EchoPackageHandler { + return &EchoPackageHandler{} +} + +func (h *EchoPackageHandler) Read(ss getty.Session, data []byte) (interface{}, int, error) { + var ( + err error + len int + pkg EchoPackage + buf *bytes.Buffer + ) + + buf = bytes.NewBuffer(data) + len, err = pkg.Unmarshal(buf) + if err != nil { + if err == ErrNotEnoughStream { + return nil, 0, nil + } + + return nil, 0, err + } + + return &pkg, len, nil +} + +func (h *EchoPackageHandler) Write(ss getty.Session, pkg interface{}) ([]byte, error) { + var ( + ok bool + err error + startTime time.Time + echoPkg *EchoPackage + buf *bytes.Buffer + ) + + startTime = time.Now() + if echoPkg, ok = pkg.(*EchoPackage); !ok { + log.Error("illegal pkg:%+v\n", pkg) + return nil, errors.New("invalid echo package!") + } + + buf, err = echoPkg.Marshal() + if err != nil { + log.Warn("binary.Write(echoPkg{%#v}) = err{%#v}", echoPkg, err) + return nil, err + } + + log.Debug("WriteEchoPkgTimeMs = %s", time.Since(startTime).String()) + + return buf.Bytes(), nil +} diff --git a/examples/echo/tcp-echo/server/app/server.go b/examples/echo/tcp-echo/server/app/server.go new file mode 100644 index 00000000..1fcaf8ea --- /dev/null +++ b/examples/echo/tcp-echo/server/app/server.go @@ -0,0 +1,191 @@ +/* + * 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. + */ + +package main + +import ( + // "flag" + "fmt" + "net" + "net/http" + _ "net/http/pprof" + "os" + "os/signal" + + // "strings" + "syscall" + "time" +) + +import ( + gxlog "github.com/AlexStocks/goext/log" + gxnet "github.com/AlexStocks/goext/net" + log "github.com/AlexStocks/log4go" + getty "github.com/apache/dubbo-getty" + "github.com/dubbogo/gost/sync" +) + +const ( + pprofPath = "/debug/pprof/" +) + +var ( +// host = flag.String("host", "127.0.0.1", "local host address that server app will use") +// ports = flag.String("ports", "12345,12346,12347", "local host port list that the server app will bind") +) + +var ( + serverList []getty.Server + taskPool gxsync.GenericTaskPool +) + +func main() { + // flag.Parse() + // if *host == "" || *ports == "" { + // panic(fmt.Sprintf("Please intput local host ip or port lists")) + // } + + initConf() + + initProfiling() + + taskPool = gxsync.NewTaskPoolSimple(0) + initServer() + gxlog.CInfo("%s starts successfull! its listen ends=%s:%s\n", + conf.AppName, conf.Host, conf.Ports) + log.Info("%s starts successfull! its listen ends=%s:%s\n", + conf.AppName, conf.Host, conf.Ports) + + initSignal() +} + +func initProfiling() { + var addr string + + // addr = *host + ":" + "10000" + addr = gxnet.HostAddress(conf.Host, conf.ProfilePort) + log.Info("App Profiling startup on address{%v}", addr+pprofPath) + go func() { + log.Info(http.ListenAndServe(addr, nil)) + }() +} + +func newSession(session getty.Session) error { + var ( + ok bool + tcpConn *net.TCPConn + ) + + if conf.GettySessionParam.CompressEncoding { + session.SetCompressType(getty.CompressZip) + } + + if tcpConn, ok = session.Conn().(*net.TCPConn); !ok { + panic(fmt.Sprintf("%s, session.conn{%#v} is not tcp connection\n", session.Stat(), session.Conn())) + } + + tcpConn.SetNoDelay(conf.GettySessionParam.TcpNoDelay) + tcpConn.SetKeepAlive(conf.GettySessionParam.TcpKeepAlive) + if conf.GettySessionParam.TcpKeepAlive { + tcpConn.SetKeepAlivePeriod(conf.GettySessionParam.keepAlivePeriod) + } + tcpConn.SetReadBuffer(conf.GettySessionParam.TcpRBufSize) + tcpConn.SetWriteBuffer(conf.GettySessionParam.TcpWBufSize) + + session.SetName(conf.GettySessionParam.SessionName) + session.SetMaxMsgLen(conf.GettySessionParam.MaxMsgLen) + session.SetPkgHandler(echoPkgHandler) + session.SetEventListener(echoMsgHandler) + session.SetReadTimeout(conf.GettySessionParam.tcpReadTimeout) + session.SetWriteTimeout(conf.GettySessionParam.tcpWriteTimeout) + session.SetCronPeriod((int)(conf.sessionTimeout.Nanoseconds() / 1e6)) + session.SetWaitTime(conf.GettySessionParam.waitTimeout) + // session.SetTaskPool(taskPool) + log.Debug("app accepts new session:%s\n", session.Stat()) + + return nil +} + +func initServer() { + var ( + addr string + portList []string + server getty.Server + ) + + // if *host == "" { + // panic("host can not be nil") + // } + // if *ports == "" { + // panic("ports can not be nil") + // } + + // portList = strings.Split(*ports, ",") + + portList = conf.Ports + if len(portList) == 0 { + panic("portList is nil") + } + for _, port := range portList { + addr = gxnet.HostAddress2(conf.Host, port) + serverOpts := []getty.ServerOption{getty.WithLocalAddress(addr)} + serverOpts = append(serverOpts, getty.WithServerTaskPool(taskPool)) + server = getty.NewTCPServer(serverOpts...) + // run server + server.RunEventLoop(newSession) + log.Debug("server bind addr{%s} ok!", addr) + serverList = append(serverList, server) + } +} + +func uninitServer() { + for _, server := range serverList { + server.Close() + } + if taskPool != nil { + taskPool.Close() + } +} + +func initSignal() { + // signal.Notify的ch信道是阻塞的(signal.Notify不会阻塞发送信号), 需要设置缓冲 + signals := make(chan os.Signal, 1) + // It is not possible to block SIGKILL or syscall.SIGSTOP + signal.Notify(signals, os.Interrupt, os.Kill, syscall.SIGHUP, syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT) + for { + sig := <-signals + log.Info("get signal %s", sig.String()) + switch sig { + case syscall.SIGHUP: + // reload() + default: + go time.AfterFunc(conf.failFastTimeout, func() { + // log.Warn("app exit now by force...") + // os.Exit(1) + log.Exit("app exit now by force...") + log.Close() + }) + + // 要么fastFailTimeout时间内执行完毕下面的逻辑然后程序退出,要么执行上面的超时函数程序强行退出 + uninitServer() + // fmt.Println("app exit now...") + log.Exit("app exit now...") + log.Close() + return + } + } +} diff --git a/examples/echo/tcp-echo/server/assembly/bin/load.sh b/examples/echo/tcp-echo/server/assembly/bin/load.sh new file mode 100644 index 00000000..1d038cc0 --- /dev/null +++ b/examples/echo/tcp-echo/server/assembly/bin/load.sh @@ -0,0 +1,186 @@ +#!/usr/bin/env bash +# ****************************************************** +# DESC : getty app devops script +# AUTHOR : Alex Stocks +# VERSION : 1.0 +# LICENCE : LGPL V3 +# EMAIL : alexstocks@foxmail.com +# MOD : 2016-05-13 02:01 +# FILE : load.sh +# ****************************************************** + +APP_NAME="APPLICATION_NAME" +APP_ARGS="" +SLEEP_INTERVAL=5 +MAX_LIFETIME=4000 + +PROJECT_HOME="" +OS_NAME=`uname` +if [[ ${OS_NAME} != "Windows" ]]; then + PROJECT_HOME=`pwd` + PROJECT_HOME=${PROJECT_HOME}"/" +else + APP_NAME="APPLICATION_NAME.exe" +fi + +export APP_CONF_FILE=${PROJECT_HOME}"TARGET_CONF_FILE" +export APP_LOG_CONF_FILE=${PROJECT_HOME}"TARGET_LOG_CONF_FILE" +# export GOTRACEBACK=system +# export GODEBUG=gctrace=1 + +usage() { + echo "Usage: $0 start" + echo " $0 stop" + echo " $0 term" + echo " $0 restart" + echo " $0 list" + echo " $0 monitor" + echo " $0 crontab" + exit +} + +start() { + APP_LOG_PATH=${PROJECT_HOME}"logs/" + mkdir -p ${APP_LOG_PATH} + APP_BIN=${PROJECT_HOME}sbin/${APP_NAME} + chmod u+x ${APP_BIN} + # CMD="nohup ${APP_BIN} ${APP_ARGS} >>${APP_NAME}.nohup.out 2>&1 &" + CMD="${APP_BIN}" + eval ${CMD} + PID=`ps aux | grep -w ${APP_NAME} | grep -v grep | awk '{print $2}'` + if [[ ${OS_NAME} != "Linux" && ${OS_NAME} != "Darwin" ]]; then + PID=`ps aux | grep -w ${APP_NAME} | grep -v grep | awk '{print $1}'` + fi + CUR=`date +%FT%T` + if [ "${PID}" != "" ]; then + for p in ${PID} + do + echo "start ${APP_NAME} ( pid =" ${p} ") at " ${CUR} + done + fi +} + +stop() { + PID=`ps aux | grep -w ${APP_NAME} | grep -v grep | awk '{print $2}'` + if [[ ${OS_NAME} != "Linux" && ${OS_NAME} != "Darwin" ]]; then + PID=`ps aux | grep -w ${APP_NAME} | grep -v grep | awk '{print $1}'` + fi + if [ "${PID}" != "" ]; + then + for ps in ${PID} + do + echo "kill -SIGINT ${APP_NAME} ( pid =" ${ps} ")" + kill -2 ${ps} + done + fi +} + + +term() { + PID=`ps aux | grep -w ${APP_NAME} | grep -v grep | awk '{print $2}'` + if [[ ${OS_NAME} != "Linux" && ${OS_NAME} != "Darwin" ]]; then + PID=`ps aux | grep -w ${APP_NAME} | grep -v grep | awk '{print $1}'` + fi + if [ "${PID}" != "" ]; + then + for ps in ${PID} + do + echo "kill -9 ${APP_NAME} ( pid =" ${ps} ")" + kill -9 ${ps} + done + fi +} + +list() { + PID=`ps aux | grep -w ${APP_NAME} | grep -v grep | awk '{printf("%s,%s,%s,%s\n", $1, $2, $9, $10)}'` + if [[ ${OS_NAME} != "Linux" && ${OS_NAME} != "Darwin" ]]; then + PID=`ps aux | grep -w ${APP_NAME} | grep -v grep | awk '{printf("%s,%s,%s,%s,%s\n", $1, $4, $6, $7, $8)}'` + fi + + if [ "${PID}" != "" ]; then + echo "list ${APP_NAME}" + + if [[ ${OS_NAME} == "Linux" || ${OS_NAME} == "Darwin" ]]; then + echo "index: user, pid, start, duration" + else + echo "index: PID, WINPID, UID, STIME, COMMAND" + fi + idx=0 + for ps in ${PID} + do + echo "${idx}: ${ps}" + ((idx ++)) + done + fi +} + +monitor() { + idx=0 + while true; do + PID=`ps aux | grep -w ${APP_NAME} | grep -v grep | awk '{print $2}'` + if [[ ${OS_NAME} != "Linux" && ${OS_NAME} != "Darwin" ]]; then + PID=`ps aux | grep -w ${APP_NAME} | grep -v grep | awk '{print $1}'` + fi + if [[ "${PID}" == "" ]]; then + start + idx=0 + fi + + ((LIFE=idx*${SLEEP_INTERVAL})) + echo "${APP_NAME} ( pid = " ${PID} ") has been working in normal state for " $LIFE " seconds." + ((idx ++)) + sleep ${SLEEP_INTERVAL} + done +} + +crontab() { + idx=0 + while true; do + PID=`ps aux | grep -w ${APP_NAME} | grep -v grep | awk '{print $2}'` + if [[ ${OS_NAME} != "Linux" && ${OS_NAME} != "Darwin" ]]; then + PID=`ps aux | grep -w ${APP_NAME} | grep -v grep | awk '{print $1}'` + fi + if [[ "${PID}" == "" ]]; then + start + idx=0 + fi + + ((LIFE=idx*${SLEEP_INTERVAL})) + echo "${APP_NAME} ( pid = " ${PID} ") has been working in normal state for " $LIFE " seconds." + ((idx ++)) + sleep ${SLEEP_INTERVAL} + if [[ ${LIFE} -gt ${MAX_LIFETIME} ]]; then + kill -9 ${PID} + fi + done +} + +opt=$1 +case C"$opt" in + Cstart) + start + ;; + Cstop) + stop + ;; + Cterm) + term + ;; + Crestart) + term + start + ;; + Clist) + list + ;; + Cmonitor) + monitor + ;; + Ccrontab) + crontab + ;; + C*) + usage + ;; +esac + diff --git a/examples/echo/tcp-echo/server/assembly/common/app.properties b/examples/echo/tcp-echo/server/assembly/common/app.properties new file mode 100644 index 00000000..914721bf --- /dev/null +++ b/examples/echo/tcp-echo/server/assembly/common/app.properties @@ -0,0 +1,17 @@ +# getty application configure script +# ****************************************************** +# DESC : application environment variable +# AUTHOR : Alex Stocks +# VERSION : 1.0 +# LICENCE : Apache License 2.0 +# EMAIL : alexstocks@foxmail.com +# MOD : 2016-07-12 16:29 +# FILE : app.properties +# ****************************************************** + +export TARGET_EXEC_NAME="echo_server" +export BUILD_PACKAGE="app" + +export TARGET_CONF_FILE="conf/config.yml" +export TARGET_LOG_CONF_FILE="conf/log.xml" + diff --git a/examples/echo/tcp-echo/server/assembly/common/build.sh b/examples/echo/tcp-echo/server/assembly/common/build.sh new file mode 100644 index 00000000..00763725 --- /dev/null +++ b/examples/echo/tcp-echo/server/assembly/common/build.sh @@ -0,0 +1,77 @@ +#!/usr/bin/env bash +# ****************************************************** +# DESC : build script +# AUTHOR : Alex Stocks +# VERSION : 1.0 +# LICENCE : Apache License 2.0 +# EMAIL : alexstocks@foxmail.com +# MOD : 2016-07-12 16:28 +# FILE : build.sh +# ****************************************************** + +rm -rf target/ + +PROJECT_HOME=`pwd` +TARGET_FOLDER=${PROJECT_HOME}/target/${GOOS} + +TARGET_SBIN_NAME=${TARGET_EXEC_NAME} +version=`cat app/version.go | grep Version | awk -F '=' '{print $2}' | awk -F '"' '{print $2}'` +if [[ ${GOOS} == "windows" ]]; then + TARGET_SBIN_NAME=${TARGET_SBIN_NAME}.exe +fi +TARGET_NAME=${TARGET_FOLDER}/${TARGET_SBIN_NAME} +if [[ $PROFILE == "dev" || $PROFILE == "test" ]]; then + # GFLAGS=-gcflags "-N -l" -race -x -v # -x会把go build的详细过程输出 + # GFLAGS=-gcflags "-N -l" -race -v + # GFLAGS="-gcflags \"-N -l\" -v" + cd ${BUILD_PACKAGE} && GOOS=$GOOS GOARCH=$GOARCH go build -gcflags "-N -l" -x -v -i -o ${TARGET_NAME} && cd - +else + # -s去掉符号表(然后panic时候的stack trace就没有任何文件名/行号信息了,这个等价于普通C/C++程序被strip的效果), + # -w去掉DWARF调试信息,得到的程序就不能用gdb调试了。-s和-w也可以分开使用,一般来说如果不打算用gdb调试, + # -w基本没啥损失。-s的损失就有点大了。 + cd ${BUILD_PACKAGE} && GOOS=$GOOS GOARCH=$GOARCH go build -ldflags "-w" -x -v -i -o ${TARGET_NAME} && cd - +fi + +TAR_NAME=${TARGET_EXEC_NAME}-${version}-`date "+%Y%m%d-%H%M"`-${PROFILE} + +mkdir -p ${TARGET_FOLDER}/${TAR_NAME} + +SBIN_DIR=${TARGET_FOLDER}/${TAR_NAME}/sbin +BIN_DIR=${TARGET_FOLDER}/${TAR_NAME} +CONF_DIR=${TARGET_FOLDER}/${TAR_NAME}/conf + +mkdir -p ${SBIN_DIR} +mkdir -p ${CONF_DIR} + +mv ${TARGET_NAME} ${SBIN_DIR} +cp -r assembly/bin ${BIN_DIR} +cd ${BIN_DIR}/bin/ && mv load.sh load_${TARGET_EXEC_NAME}.sh && cd - + +platform=$(uname) +# modify APPLICATION_NAME +if [ ${platform} == "Darwin" ]; then + sed -i "" "s~APPLICATION_NAME~${TARGET_EXEC_NAME}~g" ${BIN_DIR}/bin/* +else + sed -i "s~APPLICATION_NAME~${TARGET_EXEC_NAME}~g" ${BIN_DIR}/bin/* +fi + +# modify TARGET_CONF_FILE +if [ ${platform} == "Darwin" ]; then + sed -i "" "s~TARGET_CONF_FILE~${TARGET_CONF_FILE}~g" ${BIN_DIR}/bin/* +else + sed -i "s~TARGET_CONF_FILE~${TARGET_CONF_FILE}~g" ${BIN_DIR}/bin/* +fi + +# modify TARGET_LOG_CONF_FILE +if [ ${platform} == "Darwin" ]; then + sed -i "" "s~TARGET_LOG_CONF_FILE~${TARGET_LOG_CONF_FILE}~g" ${BIN_DIR}/bin/* +else + sed -i "s~TARGET_LOG_CONF_FILE~${TARGET_LOG_CONF_FILE}~g" ${BIN_DIR}/bin/* +fi + +cp -r profiles/${PROFILE}/* ${CONF_DIR} + +cd ${TARGET_FOLDER} + +tar czf ${TAR_NAME}.tar.gz ${TAR_NAME}/* + diff --git a/examples/echo/tcp-echo/server/assembly/linux/dev.sh b/examples/echo/tcp-echo/server/assembly/linux/dev.sh new file mode 100644 index 00000000..62c82fe6 --- /dev/null +++ b/examples/echo/tcp-echo/server/assembly/linux/dev.sh @@ -0,0 +1,28 @@ +#!/usr/bin/env bash +# ****************************************************** +# DESC : build script for dev env +# AUTHOR : Alex Stocks +# VERSION : 1.0 +# LICENCE : Apache License 2.0 +# EMAIL : alexstocks@foxmail.com +# MOD : 2016-07-12 16:34 +# FILE : dev.sh +# ****************************************************** + + +set -e + +export GOOS=linux +export GOARCH=amd64 + +export PROFILE="dev" +export PROJECT_HOME=`pwd` + +if [ -f "${PROJECT_HOME}/assembly/common/app.properties" ]; then + . ${PROJECT_HOME}/assembly/common/app.properties +fi + + +if [ -f "${PROJECT_HOME}/assembly/common/build.sh" ]; then + sh ${PROJECT_HOME}/assembly/common/build.sh +fi diff --git a/examples/echo/tcp-echo/server/assembly/linux/release.sh b/examples/echo/tcp-echo/server/assembly/linux/release.sh new file mode 100755 index 00000000..2aeaf6d9 --- /dev/null +++ b/examples/echo/tcp-echo/server/assembly/linux/release.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env bash +# ****************************************************** +# DESC : build script for release env +# AUTHOR : Alex Stocks +# VERSION : 1.0 +# LICENCE : Apache License 2.0 +# EMAIL : alexstocks@foxmail.com +# MOD : 2016-07-12 16:34 +# FILE : test.sh +# ****************************************************** + + +set -e + +export GOOS=linux +export GOARCH=amd64 + +export PROFILE="release" +export PROJECT_HOME=`pwd` + +if [ -f "${PROJECT_HOME}/assembly/common/app.properties" ]; then + . ${PROJECT_HOME}/assembly/common/app.properties +fi + +if [ -f "${PROJECT_HOME}/assembly/common/build.sh" ]; then + sh ${PROJECT_HOME}/assembly/common/build.sh +fi diff --git a/examples/echo/tcp-echo/server/assembly/linux/test.sh b/examples/echo/tcp-echo/server/assembly/linux/test.sh new file mode 100644 index 00000000..1bbbefd1 --- /dev/null +++ b/examples/echo/tcp-echo/server/assembly/linux/test.sh @@ -0,0 +1,28 @@ +#!/usr/bin/env bash +# ****************************************************** +# DESC : build script for test env +# AUTHOR : Alex Stocks +# VERSION : 1.0 +# LICENCE : Apache License 2.0 +# EMAIL : alexstocks@foxmail.com +# MOD : 2016-07-12 16:34 +# FILE : test.sh +# ****************************************************** + + +set -e + +export GOOS=linux +export GOARCH=amd64 + +export PROFILE="test" +export PROJECT_HOME=`pwd` + +if [ -f "${PROJECT_HOME}/assembly/common/app.properties" ]; then + . ${PROJECT_HOME}/assembly/common/app.properties +fi + + +if [ -f "${PROJECT_HOME}/assembly/common/build.sh" ]; then + sh ${PROJECT_HOME}/assembly/common/build.sh +fi diff --git a/examples/echo/tcp-echo/server/assembly/mac/dev.sh b/examples/echo/tcp-echo/server/assembly/mac/dev.sh new file mode 100644 index 00000000..65e46867 --- /dev/null +++ b/examples/echo/tcp-echo/server/assembly/mac/dev.sh @@ -0,0 +1,28 @@ +#!/usr/bin/env bash +# ****************************************************** +# DESC : build script for dev env +# AUTHOR : Alex Stocks +# VERSION : 1.0 +# LICENCE : Apache License 2.0 +# EMAIL : alexstocks@foxmail.com +# MOD : 2016-07-12 16:34 +# FILE : dev.sh +# ****************************************************** + + +set -e + +export GOOS=darwin +export GOARCH=amd64 + +export PROFILE="dev" +export PROJECT_HOME=`pwd` + +if [ -f "${PROJECT_HOME}/assembly/common/app.properties" ]; then + . ${PROJECT_HOME}/assembly/common/app.properties +fi + + +if [ -f "${PROJECT_HOME}/assembly/common/build.sh" ]; then + sh ${PROJECT_HOME}/assembly/common/build.sh +fi diff --git a/examples/echo/tcp-echo/server/assembly/mac/release.sh b/examples/echo/tcp-echo/server/assembly/mac/release.sh new file mode 100755 index 00000000..688288b3 --- /dev/null +++ b/examples/echo/tcp-echo/server/assembly/mac/release.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env bash +# ****************************************************** +# DESC : build script for release env +# AUTHOR : Alex Stocks +# VERSION : 1.0 +# LICENCE : Apache License 2.0 +# EMAIL : alexstocks@foxmail.com +# MOD : 2016-07-12 16:34 +# FILE : test.sh +# ****************************************************** + + +set -e + +export GOOS=darwin +export GOARCH=amd64 + +export PROFILE="release" +export PROJECT_HOME=`pwd` + +if [ -f "${PROJECT_HOME}/assembly/common/app.properties" ]; then + . ${PROJECT_HOME}/assembly/common/app.properties +fi + +if [ -f "${PROJECT_HOME}/assembly/common/build.sh" ]; then + sh ${PROJECT_HOME}/assembly/common/build.sh +fi diff --git a/examples/echo/tcp-echo/server/assembly/mac/test.sh b/examples/echo/tcp-echo/server/assembly/mac/test.sh new file mode 100644 index 00000000..56d6c11e --- /dev/null +++ b/examples/echo/tcp-echo/server/assembly/mac/test.sh @@ -0,0 +1,28 @@ +#!/usr/bin/env bash +# ****************************************************** +# DESC : build script for test env +# AUTHOR : Alex Stocks +# VERSION : 1.0 +# LICENCE : Apache License 2.0 +# EMAIL : alexstocks@foxmail.com +# MOD : 2016-07-12 16:34 +# FILE : test.sh +# ****************************************************** + + +set -e + +export GOOS=darwin +export GOARCH=amd64 + +export PROFILE="test" +export PROJECT_HOME=`pwd` + +if [ -f "${PROJECT_HOME}/assembly/common/app.properties" ]; then + . ${PROJECT_HOME}/assembly/common/app.properties +fi + + +if [ -f "${PROJECT_HOME}/assembly/common/build.sh" ]; then + sh ${PROJECT_HOME}/assembly/common/build.sh +fi diff --git a/examples/echo/tcp-echo/server/assembly/windows/dev.sh b/examples/echo/tcp-echo/server/assembly/windows/dev.sh new file mode 100644 index 00000000..94ed49ea --- /dev/null +++ b/examples/echo/tcp-echo/server/assembly/windows/dev.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env bash +# ****************************************************** +# DESC : build script for dev env +# AUTHOR : Alex Stocks +# VERSION : 1.0 +# LICENCE : Apache License 2.0 +# EMAIL : alexstocks@foxmail.com +# MOD : 2016-07-12 16:34 +# FILE : dev.sh +# ****************************************************** + + +set -e + +export GOOS=windows +export GOARCH=amd64 + +export PROFILE="dev" +export PROJECT_HOME=`pwd` + +if [ -f "${PROJECT_HOME}/assembly/common/app.properties" ]; then + . ${PROJECT_HOME}/assembly/common/app.properties +fi + +if [ -f "${PROJECT_HOME}/assembly/common/build.sh" ]; then + sh ${PROJECT_HOME}/assembly/common/build.sh +fi diff --git a/examples/echo/tcp-echo/server/assembly/windows/release.sh b/examples/echo/tcp-echo/server/assembly/windows/release.sh new file mode 100755 index 00000000..f317720b --- /dev/null +++ b/examples/echo/tcp-echo/server/assembly/windows/release.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env bash +# ****************************************************** +# DESC : build script for release env +# AUTHOR : Alex Stocks +# VERSION : 1.0 +# LICENCE : Apache License 2.0 +# EMAIL : alexstocks@foxmail.com +# MOD : 2016-07-12 16:34 +# FILE : test.sh +# ****************************************************** + + +set -e + +export GOOS=windows +export GOARCH=amd64 + +export PROFILE="release" +export PROJECT_HOME=`pwd` + +if [ -f "${PROJECT_HOME}/assembly/common/app.properties" ]; then + . ${PROJECT_HOME}/assembly/common/app.properties +fi + +if [ -f "${PROJECT_HOME}/assembly/common/build.sh" ]; then + sh ${PROJECT_HOME}/assembly/common/build.sh +fi diff --git a/examples/echo/tcp-echo/server/assembly/windows/test.sh b/examples/echo/tcp-echo/server/assembly/windows/test.sh new file mode 100644 index 00000000..0edc2bff --- /dev/null +++ b/examples/echo/tcp-echo/server/assembly/windows/test.sh @@ -0,0 +1,28 @@ +#!/usr/bin/env bash +# ****************************************************** +# DESC : build script for test env +# AUTHOR : Alex Stocks +# VERSION : 1.0 +# LICENCE : Apache License 2.0 +# EMAIL : alexstocks@foxmail.com +# MOD : 2016-07-12 16:34 +# FILE : test.sh +# ****************************************************** + + +set -e + +export GOOS=windows +export GOARCH=amd64 + +export PROFILE="test" +export PROJECT_HOME=`pwd` + +if [ -f "${PROJECT_HOME}/assembly/common/app.properties" ]; then + . ${PROJECT_HOME}/assembly/common/app.properties +fi + + +if [ -f "${PROJECT_HOME}/assembly/common/build.sh" ]; then + sh ${PROJECT_HOME}/assembly/common/build.sh +fi diff --git a/examples/echo/tcp-echo/server/profiles/dev/config.yml b/examples/echo/tcp-echo/server/profiles/dev/config.yml new file mode 100644 index 00000000..9b5f5486 --- /dev/null +++ b/examples/echo/tcp-echo/server/profiles/dev/config.yml @@ -0,0 +1,33 @@ +# server yaml configure file +app_name : "ECHO-SERVER" +host : "127.0.0.1" +ports : ["10000", "20000"] +profile_port : 10086 + +# session +# client与server之间连接的超时时间 +session_timeout : "20s" +session_number : 700 + +# app +fail_fast_timeout : "3s" + +# tcp +getty_session_param: + compress_encoding : true + tcp_no_delay : true + tcp_keep_alive : true + keep_alive_period : "120s" + tcp_r_buf_size : 262144 + tcp_w_buf_size : 524288 + pkg_rq_size : 1024 + pkg_wq_size : 512 + tcp_read_timeout : "1s" + tcp_write_timeout : "5s" + wait_timeout : "1s" + max_msg_len : 128 + session_name : "echo-server" + +task_queue_length : 1024 +task_queue_number : 20 +task_pool_size : 1000 \ No newline at end of file diff --git a/examples/echo/tcp-echo/server/profiles/dev/log.xml b/examples/echo/tcp-echo/server/profiles/dev/log.xml new file mode 100644 index 00000000..8b84716c --- /dev/null +++ b/examples/echo/tcp-echo/server/profiles/dev/log.xml @@ -0,0 +1,63 @@ + + + stdout + console + + DEBUG + + + debug_file + file + DEBUG + logs/debug.log + [%D %T] [%L] [%S] %M + true + 0M + 0K + true + + + info_file + file + INFO + logs/info.log + + [%D %T] [%L] [%S] %M + true + 0M + 0K + true + + + warn_file + file + WARNING + logs/warn.log + [%D %T] [%L] [%S] %M + true + 0M + 0K + true + + + error_file + file + ERROR + logs/error.log + [%D %T] [%L] [%S] %M + true + 0M + 0K + true + + diff --git a/examples/echo/tcp-echo/server/profiles/release/config.yml b/examples/echo/tcp-echo/server/profiles/release/config.yml new file mode 100644 index 00000000..9b5f5486 --- /dev/null +++ b/examples/echo/tcp-echo/server/profiles/release/config.yml @@ -0,0 +1,33 @@ +# server yaml configure file +app_name : "ECHO-SERVER" +host : "127.0.0.1" +ports : ["10000", "20000"] +profile_port : 10086 + +# session +# client与server之间连接的超时时间 +session_timeout : "20s" +session_number : 700 + +# app +fail_fast_timeout : "3s" + +# tcp +getty_session_param: + compress_encoding : true + tcp_no_delay : true + tcp_keep_alive : true + keep_alive_period : "120s" + tcp_r_buf_size : 262144 + tcp_w_buf_size : 524288 + pkg_rq_size : 1024 + pkg_wq_size : 512 + tcp_read_timeout : "1s" + tcp_write_timeout : "5s" + wait_timeout : "1s" + max_msg_len : 128 + session_name : "echo-server" + +task_queue_length : 1024 +task_queue_number : 20 +task_pool_size : 1000 \ No newline at end of file diff --git a/examples/echo/tcp-echo/server/profiles/release/log.xml b/examples/echo/tcp-echo/server/profiles/release/log.xml new file mode 100644 index 00000000..8b84716c --- /dev/null +++ b/examples/echo/tcp-echo/server/profiles/release/log.xml @@ -0,0 +1,63 @@ + + + stdout + console + + DEBUG + + + debug_file + file + DEBUG + logs/debug.log + [%D %T] [%L] [%S] %M + true + 0M + 0K + true + + + info_file + file + INFO + logs/info.log + + [%D %T] [%L] [%S] %M + true + 0M + 0K + true + + + warn_file + file + WARNING + logs/warn.log + [%D %T] [%L] [%S] %M + true + 0M + 0K + true + + + error_file + file + ERROR + logs/error.log + [%D %T] [%L] [%S] %M + true + 0M + 0K + true + + diff --git a/examples/echo/tcp-echo/server/profiles/test/config.yml b/examples/echo/tcp-echo/server/profiles/test/config.yml new file mode 100644 index 00000000..9b5f5486 --- /dev/null +++ b/examples/echo/tcp-echo/server/profiles/test/config.yml @@ -0,0 +1,33 @@ +# server yaml configure file +app_name : "ECHO-SERVER" +host : "127.0.0.1" +ports : ["10000", "20000"] +profile_port : 10086 + +# session +# client与server之间连接的超时时间 +session_timeout : "20s" +session_number : 700 + +# app +fail_fast_timeout : "3s" + +# tcp +getty_session_param: + compress_encoding : true + tcp_no_delay : true + tcp_keep_alive : true + keep_alive_period : "120s" + tcp_r_buf_size : 262144 + tcp_w_buf_size : 524288 + pkg_rq_size : 1024 + pkg_wq_size : 512 + tcp_read_timeout : "1s" + tcp_write_timeout : "5s" + wait_timeout : "1s" + max_msg_len : 128 + session_name : "echo-server" + +task_queue_length : 1024 +task_queue_number : 20 +task_pool_size : 1000 \ No newline at end of file diff --git a/examples/echo/tcp-echo/server/profiles/test/log.xml b/examples/echo/tcp-echo/server/profiles/test/log.xml new file mode 100644 index 00000000..8b84716c --- /dev/null +++ b/examples/echo/tcp-echo/server/profiles/test/log.xml @@ -0,0 +1,63 @@ + + + stdout + console + + DEBUG + + + debug_file + file + DEBUG + logs/debug.log + [%D %T] [%L] [%S] %M + true + 0M + 0K + true + + + info_file + file + INFO + logs/info.log + + [%D %T] [%L] [%S] %M + true + 0M + 0K + true + + + warn_file + file + WARNING + logs/warn.log + [%D %T] [%L] [%S] %M + true + 0M + 0K + true + + + error_file + file + ERROR + logs/error.log + [%D %T] [%L] [%S] %M + true + 0M + 0K + true + + diff --git a/examples/echo/udp-echo/change_log.md b/examples/echo/udp-echo/change_log.md new file mode 100644 index 00000000..5cc42a39 --- /dev/null +++ b/examples/echo/udp-echo/change_log.md @@ -0,0 +1,41 @@ +# udp-echo # +--- +*getty udp examples of Echo Example* + +## LICENSE ## +--- + +> LICENCE : Apache License 2.0 + +## develop history ## +--- + +- 2019/09/05 + > feature + * add writev + +- 2018/06/24 + > improvement + * delete this, using name abbreviation instead. + +- 2018/03/17 + > improvement + * set the timeout parameter of WritePkg 0 to send out package asap. + +- 2018/03/17 + > improvement + * UDP_ENDPOINT session should be long live. + +- 2018/03/17 + > improvement + * use getty v0.8.2 + +- 2018/03/16 + > bug fix + * forbid close udp session in OnCron + +- 2018/03/14 + > init + + + diff --git a/examples/echo/udp-echo/client/app/client.go b/examples/echo/udp-echo/client/app/client.go new file mode 100644 index 00000000..28609342 --- /dev/null +++ b/examples/echo/udp-echo/client/app/client.go @@ -0,0 +1,185 @@ +/* + * 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. + */ + +package main + +import ( + "math/rand" + "net" + "sync" + "sync/atomic" + "time" +) + +import ( + log "github.com/AlexStocks/log4go" + getty "github.com/apache/dubbo-getty" +) + +var ( + reqID uint32 + src = rand.NewSource(time.Now().UnixNano()) +) + +func init() { + rand.Seed(time.Now().UnixNano()) +} + +//////////////////////////////////////////////////////////////////// +// echo client +//////////////////////////////////////////////////////////////////// + +type EchoClient struct { + lock sync.RWMutex + sessions []*clientEchoSession + gettyClient getty.Client + serverAddr net.UDPAddr +} + +func (c *EchoClient) isAvailable() bool { + if c.selectSession() == nil { + return false + } + + return true +} + +func (c *EchoClient) close() { + c.lock.Lock() + defer c.lock.Unlock() + if c.gettyClient != nil { + c.gettyClient.Close() + c.gettyClient = nil + for _, s := range c.sessions { + log.Info("close client session{%s, last active:%s, request number:%d}", + s.session.Stat(), s.session.GetActive().String(), s.reqNum) + s.session.Close() + } + c.sessions = c.sessions[:0] + } +} + +func (c *EchoClient) selectSession() getty.Session { + // get route server session + c.lock.RLock() + defer c.lock.RUnlock() + count := len(c.sessions) + if count == 0 { + log.Warn("client session array is nil...") + return nil + } + + return c.sessions[rand.Int31n(int32(count))].session +} + +func (c *EchoClient) addSession(session getty.Session) { + log.Debug("add session{%s}", session.Stat()) + if session == nil { + return + } + + c.lock.Lock() + c.sessions = append(c.sessions, &clientEchoSession{session: session}) + log.Debug("after add session{%s}, session number:%d", session.Stat(), len(c.sessions)) + c.lock.Unlock() +} + +func (c *EchoClient) removeSession(session getty.Session) { + if session == nil { + return + } + + c.lock.Lock() + + for i, s := range c.sessions { + if s.session == session { + c.sessions = append(c.sessions[:i], c.sessions[i+1:]...) + log.Debug("delete session{%s}, its index{%d}", session.Stat(), i) + break + } + } + log.Info("after remove session{%s}, left session number:%d", session.Stat(), len(c.sessions)) + + c.lock.Unlock() +} + +func (c *EchoClient) updateSession(session getty.Session) { + if session == nil { + return + } + + c.lock.Lock() + + for i, s := range c.sessions { + if s.session == session { + c.sessions[i].reqNum++ + break + } + } + + c.lock.Unlock() +} + +func (c *EchoClient) getClientEchoSession(session getty.Session) (clientEchoSession, error) { + var ( + err error + echoSession clientEchoSession + ) + + c.lock.Lock() + + err = errSessionNotExist + for _, s := range c.sessions { + if s.session == session { + echoSession = *s + err = nil + break + } + } + + c.lock.Unlock() + + return echoSession, err +} + +func (c *EchoClient) heartbeat(session getty.Session) { + var ( + err error + pkg EchoPackage + ctx getty.UDPContext + ) + + pkg.H.Magic = echoPkgMagic + pkg.H.LogID = (uint32)(src.Int63()) + pkg.H.Sequence = atomic.AddUint32(&reqID, 1) + // pkg.H.ServiceID = 0 + pkg.H.Command = heartbeatCmd + pkg.B = echoHeartbeatRequestString + pkg.H.Len = (uint16)(len(pkg.B) + 1) + + ctx.Pkg = &pkg + ctx.PeerAddr = &(c.serverAddr) + + // if err := session.WritePkg(ctx, WritePkgTimeout); err != nil { + if _, _, err = session.WritePkg(ctx, WritePkgASAP); err != nil { + log.Warn("session.WritePkg(session{%s}, context{%#v}) = error{%v}", session.Stat(), ctx, err) + session.Close() + + c.removeSession(session) + } + log.Debug("session.WritePkg(session{%s}, context{%#v})", session.Stat(), ctx) +} diff --git a/examples/echo/udp-echo/client/app/config.go b/examples/echo/udp-echo/client/app/config.go new file mode 100644 index 00000000..40fab211 --- /dev/null +++ b/examples/echo/udp-echo/client/app/config.go @@ -0,0 +1,155 @@ +/* + * 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. + */ + +package main + +import ( + "fmt" + "os" + "path" + "time" +) + +import ( + log "github.com/AlexStocks/log4go" + config "github.com/koding/multiconfig" +) + +const ( + APP_CONF_FILE = "APP_CONF_FILE" + APP_LOG_CONF_FILE = "APP_LOG_CONF_FILE" +) + +var conf *Config + +type ( + GettySessionParam struct { + CompressEncoding bool `default:"false"` + UdpRBufSize int `default:"262144"` + UdpWBufSize int `default:"65536"` + PkgWQSize int `default:"1024"` + UdpReadTimeout string `default:"1s"` + udpReadTimeout time.Duration + UdpWriteTimeout string `default:"5s"` + udpWriteTimeout time.Duration + WaitTimeout string `default:"7s"` + waitTimeout time.Duration + MaxMsgLen int `default:"1024"` + SessionName string `default:"echo-client"` + } + + // Config holds supported types by the multiconfig package + Config struct { + // local + AppName string `default:"echo-client"` + LocalHost string `default:"127.0.0.1"` + + // server + ServerHost string `default:"127.0.0.1"` + ServerPort int `default:"10000"` + ProfilePort int `default:"10086"` + + // client session pool + ConnectionNum int `default:"16"` + + // heartbeat + HeartbeatPeriod string `default:"15s"` + heartbeatPeriod time.Duration + + // session + SessionTimeout string `default:"60s"` + sessionTimeout time.Duration + + // echo + EchoString string `default:"hello"` + EchoTimes int `default:"10"` + + // app + FailFastTimeout string `default:"5s"` + failFastTimeout time.Duration + + // session tcp parameters + GettySessionParam GettySessionParam `required:"true"` + } +) + +func initConf() { + var ( + err error + confFile string + ) + + // configure + confFile = os.Getenv(APP_CONF_FILE) + if confFile == "" { + panic(fmt.Sprintf("application configure file name is nil")) + return // I know it is of no usage. Just Err Protection. + } + if path.Ext(confFile) != ".toml" { + panic(fmt.Sprintf("application configure file name{%v} suffix must be .toml", confFile)) + return + } + conf = new(Config) + config.MustLoadWithPath(confFile, conf) + + conf.heartbeatPeriod, err = time.ParseDuration(conf.HeartbeatPeriod) + if err != nil { + panic(fmt.Sprintf("time.ParseDuration(HeartbeatPeroid{%#v}) = error{%v}", conf.HeartbeatPeriod, err)) + return + } + conf.sessionTimeout, err = time.ParseDuration(conf.SessionTimeout) + if err != nil { + panic(fmt.Sprintf("time.ParseDuration(SessionTimeout{%#v}) = error{%v}", conf.SessionTimeout, err)) + return + } + conf.failFastTimeout, err = time.ParseDuration(conf.FailFastTimeout) + if err != nil { + panic(fmt.Sprintf("time.ParseDuration(FailFastTimeout{%#v}) = error{%v}", conf.FailFastTimeout, err)) + return + } + conf.GettySessionParam.udpReadTimeout, err = time.ParseDuration(conf.GettySessionParam.UdpReadTimeout) + if err != nil { + panic(fmt.Sprintf("time.ParseDuration(UdpReadTimeout{%#v}) = error{%v}", conf.GettySessionParam.UdpReadTimeout, err)) + return + } + conf.GettySessionParam.udpWriteTimeout, err = time.ParseDuration(conf.GettySessionParam.UdpWriteTimeout) + if err != nil { + panic(fmt.Sprintf("time.ParseDuration(UdpWriteTimeout{%#v}) = error{%v}", conf.GettySessionParam.UdpWriteTimeout, err)) + return + } + conf.GettySessionParam.waitTimeout, err = time.ParseDuration(conf.GettySessionParam.WaitTimeout) + if err != nil { + panic(fmt.Sprintf("time.ParseDuration(WaitTimeout{%#v}) = error{%v}", conf.GettySessionParam.WaitTimeout, err)) + return + } + // gxlog.Info("config{%#v}\n", conf) + + // log + confFile = os.Getenv(APP_LOG_CONF_FILE) + if confFile == "" { + panic(fmt.Sprintf("log configure file name is nil")) + return + } + if path.Ext(confFile) != ".xml" { + panic(fmt.Sprintf("log configure file name{%v} suffix must be .xml", confFile)) + return + } + log.LoadConfiguration(confFile) + log.Info("config{%#v}", conf) + + return +} diff --git a/examples/echo/udp-echo/client/app/echo.go b/examples/echo/udp-echo/client/app/echo.go new file mode 100644 index 00000000..bec9213c --- /dev/null +++ b/examples/echo/udp-echo/client/app/echo.go @@ -0,0 +1,155 @@ +/* + * 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. + */ + +package main + +import ( + "bytes" + "encoding/binary" + "errors" + "fmt" + "unsafe" +) + +import ( + log "github.com/AlexStocks/log4go" +) + +//////////////////////////////////////////// +// echo command +//////////////////////////////////////////// + +type echoCommand uint32 + +const ( + heartbeatCmd = iota + echoCmd +) + +var echoCommandStrings = [...]string{ + "heartbeat", + "echo", +} + +func (c echoCommand) String() string { + return echoCommandStrings[c] +} + +//////////////////////////////////////////// +// EchoPkgHandler +//////////////////////////////////////////// + +const ( + echoPkgMagic = 0x20160905 + maxEchoStringLen = 0xff + + echoHeartbeatRequestString = "ping" + echoHeartbeatResponseString = "pong" +) + +var ( + ErrNotEnoughStream = errors.New("packet stream is not enough") + ErrTooLargePackage = errors.New("package length is exceed the echo package's legal maximum length.") + ErrIllegalMagic = errors.New("package magic is not right.") +) + +var echoPkgHeaderLen int + +func init() { + echoPkgHeaderLen = (int)((uint)(unsafe.Sizeof(EchoPkgHeader{}))) +} + +type EchoPkgHeader struct { + Magic uint32 + LogID uint32 // log id + + Sequence uint32 // request/response sequence + ServiceID uint32 // service id + + Command uint32 // operation command code + Code int32 // error code + + Len uint16 // body length + _ uint16 + _ int32 // reserved, maybe used as package md5 checksum +} + +type EchoPackage struct { + H EchoPkgHeader + B string +} + +func (p EchoPackage) String() string { + return fmt.Sprintf("log id:%d, sequence:%d, command:%s, echo string:%s", + p.H.LogID, p.H.Sequence, (echoCommand(p.H.Command)).String(), p.B) +} + +func (p EchoPackage) Marshal() (*bytes.Buffer, error) { + var ( + err error + buf *bytes.Buffer + ) + + buf = &bytes.Buffer{} + err = binary.Write(buf, binary.LittleEndian, p.H) + if err != nil { + return nil, err + } + buf.WriteByte((byte)(len(p.B))) + buf.WriteString(p.B) + + return buf, nil +} + +func (p *EchoPackage) Unmarshal(buf *bytes.Buffer) (int, error) { + var ( + err error + len byte + ) + + if buf.Len() < echoPkgHeaderLen { + return 0, ErrNotEnoughStream + } + + // header + err = binary.Read(buf, binary.LittleEndian, &(p.H)) + if err != nil { + return 0, err + } + if p.H.Magic != echoPkgMagic { + log.Error("@p.H.Magic{%x}, right magic{%x}", p.H.Magic, echoPkgMagic) + return 0, ErrIllegalMagic + } + if buf.Len() < (int)(p.H.Len) { + return 0, ErrNotEnoughStream + } + if maxEchoStringLen < p.H.Len-1 { + return 0, ErrTooLargePackage + } + + len, err = buf.ReadByte() + if err != nil { + return 0, nil + } + p.B = (string)(buf.Next((int)(len))) + //if strings.HasPrefix(p.B, "Hello, getty!") { + // gxlog.CError("idx:%d, body:%s", idx, p.B) + // idx++ + //} + + return (int)(p.H.Len) + echoPkgHeaderLen, nil +} diff --git a/examples/echo/udp-echo/client/app/handler.go b/examples/echo/udp-echo/client/app/handler.go new file mode 100644 index 00000000..9082374a --- /dev/null +++ b/examples/echo/udp-echo/client/app/handler.go @@ -0,0 +1,99 @@ +/* + * 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. + */ + +package main + +import ( + "errors" + "time" +) + +import ( + log "github.com/AlexStocks/log4go" + getty "github.com/apache/dubbo-getty" +) + +var errSessionNotExist = errors.New("session not exist!") + +//////////////////////////////////////////// +// EchoMessageHandler +//////////////////////////////////////////// + +type clientEchoSession struct { + session getty.Session + reqNum int32 +} + +type EchoMessageHandler struct { + client *EchoClient +} + +func newEchoMessageHandler(client *EchoClient) *EchoMessageHandler { + return &EchoMessageHandler{client: client} +} + +func (h *EchoMessageHandler) OnOpen(session getty.Session) error { + h.client.addSession(session) + + return nil +} + +func (h *EchoMessageHandler) OnError(session getty.Session, err error) { + log.Info("session{%s} got error{%v}, will be closed.", session.Stat(), err) + h.client.removeSession(session) +} + +func (h *EchoMessageHandler) OnClose(session getty.Session) { + log.Info("session{%s} is closing......", session.Stat()) + h.client.removeSession(session) +} + +func (h *EchoMessageHandler) OnMessage(session getty.Session, udpCtx interface{}) { + ctx, ok := udpCtx.(getty.UDPContext) + if !ok { + log.Error("illegal UDPContext{%#v}", udpCtx) + return + } + p, ok := ctx.Pkg.(*EchoPackage) + if !ok { + log.Error("illegal packge{%#v}", ctx.Pkg) + return + } + + log.Debug("get echo package{%s}", p) + + h.client.updateSession(session) +} + +func (h *EchoMessageHandler) OnCron(session getty.Session) { + clientEchoSession, err := h.client.getClientEchoSession(session) + if err != nil { + log.Error("client.getClientSession(session{%s}) = error{%#v}", session.Stat(), err) + return + } + if conf.sessionTimeout.Nanoseconds() < time.Since(session.GetActive()).Nanoseconds() { + log.Warn("session{%s} timeout{%s}, reqNum{%d}", + session.Stat(), time.Since(session.GetActive()).String(), clientEchoSession.reqNum) + // UDP_ENDPOINT session should be long live. + if h.client != &unconnectedClient { + h.client.removeSession(session) + } + return + } + + h.client.heartbeat(session) +} diff --git a/examples/echo/udp-echo/client/app/main.go b/examples/echo/udp-echo/client/app/main.go new file mode 100644 index 00000000..d58a47b4 --- /dev/null +++ b/examples/echo/udp-echo/client/app/main.go @@ -0,0 +1,235 @@ +/* + * 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. + */ + +package main + +import ( + // "flag" + "fmt" + "net" + "net/http" + _ "net/http/pprof" + "os" + "os/signal" + "sync/atomic" + "syscall" + "time" +) + +import ( + gxlog "github.com/AlexStocks/goext/log" + gxnet "github.com/AlexStocks/goext/net" + gxtime "github.com/AlexStocks/goext/time" + log "github.com/AlexStocks/log4go" + getty "github.com/apache/dubbo-getty" +) + +const ( + pprofPath = "/debug/pprof/" +) + +const ( + WritePkgTimeout = 1e9 + WritePkgASAP = 0e9 +) + +var ( + connectedClient EchoClient + unconnectedClient EchoClient +) + +//////////////////////////////////////////////////////////////////// +// main +//////////////////////////////////////////////////////////////////// + +func main() { + initConf() + + initProfiling() + + initClient() + log.Info("%s starts successfull!\n", conf.AppName) + gxlog.CInfo("%s starts successfull!", conf.AppName) + + go test() + + initSignal() +} + +func initProfiling() { + var addr string + + addr = gxnet.HostAddress(conf.LocalHost, conf.ProfilePort) + log.Info("App Profiling startup on address{%v}", addr+pprofPath) + go func() { + log.Info(http.ListenAndServe(addr, nil)) + }() +} + +func newSession(session getty.Session) error { + var ( + ok bool + udpConn *net.UDPConn + gettyClient getty.Client + client *EchoClient + sessionName string + ) + + if gettyClient, ok = session.EndPoint().(getty.Client); !ok { + panic(fmt.Sprintf("the endpoint type of session{%#v} is not getty.Client", session)) + } + + switch gettyClient { + case connectedClient.gettyClient: + client = &connectedClient + sessionName = "connected-" + conf.GettySessionParam.SessionName + + case unconnectedClient.gettyClient: + client = &unconnectedClient + sessionName = "unconnected-" + conf.GettySessionParam.SessionName + + default: + panic(fmt.Sprintf("illegal session{%#v} endpoint", session)) + } + + if conf.GettySessionParam.CompressEncoding { + session.SetCompressType(getty.CompressZip) + } + + if udpConn, ok = session.Conn().(*net.UDPConn); !ok { + panic(fmt.Sprintf("%s, session.conn{%#v} is not udp connection\n", session.Stat(), session.Conn())) + } + + udpConn.SetReadBuffer(conf.GettySessionParam.UdpRBufSize) + udpConn.SetWriteBuffer(conf.GettySessionParam.UdpWBufSize) + + session.SetName(sessionName) + session.SetMaxMsgLen(conf.GettySessionParam.MaxMsgLen) + session.SetPkgHandler(echoPkgHandler) + session.SetEventListener(newEchoMessageHandler(client)) + session.SetReadTimeout(conf.GettySessionParam.udpReadTimeout) + session.SetWriteTimeout(conf.GettySessionParam.udpWriteTimeout) + session.SetCronPeriod((int)(conf.heartbeatPeriod.Nanoseconds() / 1e6)) + session.SetWaitTime(conf.GettySessionParam.waitTimeout) + log.Debug("client new session:%s\n", session.Stat()) + gxlog.CDebug("client new session:%s\n", session.Stat()) + + return nil +} + +func initClient() { + unconnectedClient.gettyClient = getty.NewUDPEndPoint( + getty.WithLocalAddress(gxnet.HostAddress(net.IPv4zero.String(), 0)), + ) + unconnectedClient.gettyClient.RunEventLoop(newSession) + unconnectedClient.serverAddr = net.UDPAddr{IP: net.ParseIP(conf.ServerHost), Port: conf.ServerPort} + + connectedClient.gettyClient = getty.NewUDPClient( + getty.WithServerAddress(gxnet.HostAddress(conf.ServerHost, conf.ServerPort)), + getty.WithConnectionNumber((int)(conf.ConnectionNum)), + ) + connectedClient.gettyClient.RunEventLoop(newSession) +} + +func uninitClient() { + connectedClient.close() + unconnectedClient.close() +} + +func initSignal() { + // signal.Notify的ch信道是阻塞的(signal.Notify不会阻塞发送信号), 需要设置缓冲 + signals := make(chan os.Signal, 1) + // It is not possible to block SIGKILL or syscall.SIGSTOP + signal.Notify(signals, os.Interrupt, os.Kill, syscall.SIGHUP, syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT) + for { + sig := <-signals + log.Info("get signal %s", sig.String()) + switch sig { + case syscall.SIGHUP: + // reload() + default: + go time.AfterFunc(conf.failFastTimeout, func() { + // log.Warn("app exit now by force...") + // os.Exit(1) + log.Exit("app exit now by force...") + log.Close() + }) + + // 要么fastFailTimeout时间内执行完毕下面的逻辑然后程序退出,要么执行上面的超时函数程序强行退出 + uninitClient() + // fmt.Println("app exit now...") + log.Exit("app exit now...") + log.Close() + return + } + } +} + +func echo(client *EchoClient) { + var ( + err error + pkg EchoPackage + ctx getty.UDPContext + ) + + pkg.H.Magic = echoPkgMagic + pkg.H.LogID = (uint32)(src.Int63()) + pkg.H.Sequence = atomic.AddUint32(&reqID, 1) + // pkg.H.ServiceID = 0 + pkg.H.Command = echoCmd + pkg.B = conf.EchoString + pkg.H.Len = (uint16)(len(pkg.B)) + 1 + + ctx.Pkg = &pkg + ctx.PeerAddr = &(client.serverAddr) + + if session := client.selectSession(); session != nil { + // err := session.WritePkg(ctx, WritePkgTimeout) + _, _, err = session.WritePkg(ctx, WritePkgASAP) + if err != nil { + log.Warn("session.WritePkg(session{%s}, UDPContext{%#v}) = error{%v}", session.Stat(), ctx, err) + session.Close() + client.removeSession(session) + } + } +} + +func testEchoClient(client *EchoClient) { + var ( + cost int64 + counter gxtime.CountWatch + ) + + for { + if client.isAvailable() { + break + } + time.Sleep(3e9) + } + + counter.Start() + for i := 0; i < conf.EchoTimes; i++ { + echo(client) + } + cost = counter.Count() + log.Info("after loop %d times, echo cost %d ms", conf.EchoTimes, cost/1e6) +} + +func test() { + testEchoClient(&unconnectedClient) + testEchoClient(&connectedClient) +} diff --git a/examples/echo/udp-echo/client/app/readwriter.go b/examples/echo/udp-echo/client/app/readwriter.go new file mode 100644 index 00000000..6774d684 --- /dev/null +++ b/examples/echo/udp-echo/client/app/readwriter.go @@ -0,0 +1,92 @@ +/* + * 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. + */ + +package main + +import ( + "bytes" + "errors" + "fmt" + "time" +) + +import ( + log "github.com/AlexStocks/log4go" + getty "github.com/apache/dubbo-getty" +) + +var echoPkgHandler = NewEchoPackageHandler() + +type EchoPackageHandler struct{} + +func NewEchoPackageHandler() *EchoPackageHandler { + return &EchoPackageHandler{} +} + +func (h *EchoPackageHandler) Read(ss getty.Session, data []byte) (interface{}, int, error) { + var ( + err error + len int + pkg EchoPackage + buf *bytes.Buffer + ) + + buf = bytes.NewBuffer(data) + len, err = pkg.Unmarshal(buf) + if err != nil { + if err == ErrNotEnoughStream { + return nil, 0, nil + } + + return nil, 0, err + } + + return &pkg, len, nil +} + +func (h *EchoPackageHandler) Write(ss getty.Session, udpCtx interface{}) ([]byte, error) { + var ( + ok bool + err error + startTime time.Time + echoPkg *EchoPackage + buf *bytes.Buffer + ctx getty.UDPContext + ) + + ctx, ok = udpCtx.(getty.UDPContext) + if !ok { + log.Error("illegal UDPContext{%#v}", udpCtx) + return nil, fmt.Errorf("illegal @udpCtx{%#v}", udpCtx) + } + + startTime = time.Now() + if echoPkg, ok = ctx.Pkg.(*EchoPackage); !ok { + log.Error("illegal pkg:%+v, its type:%T\n", ctx.Pkg, ctx.Pkg) + return nil, errors.New("invalid echo package!") + } + + buf, err = echoPkg.Marshal() + if err != nil { + log.Warn("binary.Write(echoPkg{%#v}) = err{%#v}", echoPkg, err) + return nil, err + } + + log.Debug("WriteEchoPkgTimeMs = %s", time.Since(startTime).String()) + + return buf.Bytes(), nil +} diff --git a/examples/echo/udp-echo/client/assembly/bin/load.sh b/examples/echo/udp-echo/client/assembly/bin/load.sh new file mode 100644 index 00000000..1d038cc0 --- /dev/null +++ b/examples/echo/udp-echo/client/assembly/bin/load.sh @@ -0,0 +1,186 @@ +#!/usr/bin/env bash +# ****************************************************** +# DESC : getty app devops script +# AUTHOR : Alex Stocks +# VERSION : 1.0 +# LICENCE : LGPL V3 +# EMAIL : alexstocks@foxmail.com +# MOD : 2016-05-13 02:01 +# FILE : load.sh +# ****************************************************** + +APP_NAME="APPLICATION_NAME" +APP_ARGS="" +SLEEP_INTERVAL=5 +MAX_LIFETIME=4000 + +PROJECT_HOME="" +OS_NAME=`uname` +if [[ ${OS_NAME} != "Windows" ]]; then + PROJECT_HOME=`pwd` + PROJECT_HOME=${PROJECT_HOME}"/" +else + APP_NAME="APPLICATION_NAME.exe" +fi + +export APP_CONF_FILE=${PROJECT_HOME}"TARGET_CONF_FILE" +export APP_LOG_CONF_FILE=${PROJECT_HOME}"TARGET_LOG_CONF_FILE" +# export GOTRACEBACK=system +# export GODEBUG=gctrace=1 + +usage() { + echo "Usage: $0 start" + echo " $0 stop" + echo " $0 term" + echo " $0 restart" + echo " $0 list" + echo " $0 monitor" + echo " $0 crontab" + exit +} + +start() { + APP_LOG_PATH=${PROJECT_HOME}"logs/" + mkdir -p ${APP_LOG_PATH} + APP_BIN=${PROJECT_HOME}sbin/${APP_NAME} + chmod u+x ${APP_BIN} + # CMD="nohup ${APP_BIN} ${APP_ARGS} >>${APP_NAME}.nohup.out 2>&1 &" + CMD="${APP_BIN}" + eval ${CMD} + PID=`ps aux | grep -w ${APP_NAME} | grep -v grep | awk '{print $2}'` + if [[ ${OS_NAME} != "Linux" && ${OS_NAME} != "Darwin" ]]; then + PID=`ps aux | grep -w ${APP_NAME} | grep -v grep | awk '{print $1}'` + fi + CUR=`date +%FT%T` + if [ "${PID}" != "" ]; then + for p in ${PID} + do + echo "start ${APP_NAME} ( pid =" ${p} ") at " ${CUR} + done + fi +} + +stop() { + PID=`ps aux | grep -w ${APP_NAME} | grep -v grep | awk '{print $2}'` + if [[ ${OS_NAME} != "Linux" && ${OS_NAME} != "Darwin" ]]; then + PID=`ps aux | grep -w ${APP_NAME} | grep -v grep | awk '{print $1}'` + fi + if [ "${PID}" != "" ]; + then + for ps in ${PID} + do + echo "kill -SIGINT ${APP_NAME} ( pid =" ${ps} ")" + kill -2 ${ps} + done + fi +} + + +term() { + PID=`ps aux | grep -w ${APP_NAME} | grep -v grep | awk '{print $2}'` + if [[ ${OS_NAME} != "Linux" && ${OS_NAME} != "Darwin" ]]; then + PID=`ps aux | grep -w ${APP_NAME} | grep -v grep | awk '{print $1}'` + fi + if [ "${PID}" != "" ]; + then + for ps in ${PID} + do + echo "kill -9 ${APP_NAME} ( pid =" ${ps} ")" + kill -9 ${ps} + done + fi +} + +list() { + PID=`ps aux | grep -w ${APP_NAME} | grep -v grep | awk '{printf("%s,%s,%s,%s\n", $1, $2, $9, $10)}'` + if [[ ${OS_NAME} != "Linux" && ${OS_NAME} != "Darwin" ]]; then + PID=`ps aux | grep -w ${APP_NAME} | grep -v grep | awk '{printf("%s,%s,%s,%s,%s\n", $1, $4, $6, $7, $8)}'` + fi + + if [ "${PID}" != "" ]; then + echo "list ${APP_NAME}" + + if [[ ${OS_NAME} == "Linux" || ${OS_NAME} == "Darwin" ]]; then + echo "index: user, pid, start, duration" + else + echo "index: PID, WINPID, UID, STIME, COMMAND" + fi + idx=0 + for ps in ${PID} + do + echo "${idx}: ${ps}" + ((idx ++)) + done + fi +} + +monitor() { + idx=0 + while true; do + PID=`ps aux | grep -w ${APP_NAME} | grep -v grep | awk '{print $2}'` + if [[ ${OS_NAME} != "Linux" && ${OS_NAME} != "Darwin" ]]; then + PID=`ps aux | grep -w ${APP_NAME} | grep -v grep | awk '{print $1}'` + fi + if [[ "${PID}" == "" ]]; then + start + idx=0 + fi + + ((LIFE=idx*${SLEEP_INTERVAL})) + echo "${APP_NAME} ( pid = " ${PID} ") has been working in normal state for " $LIFE " seconds." + ((idx ++)) + sleep ${SLEEP_INTERVAL} + done +} + +crontab() { + idx=0 + while true; do + PID=`ps aux | grep -w ${APP_NAME} | grep -v grep | awk '{print $2}'` + if [[ ${OS_NAME} != "Linux" && ${OS_NAME} != "Darwin" ]]; then + PID=`ps aux | grep -w ${APP_NAME} | grep -v grep | awk '{print $1}'` + fi + if [[ "${PID}" == "" ]]; then + start + idx=0 + fi + + ((LIFE=idx*${SLEEP_INTERVAL})) + echo "${APP_NAME} ( pid = " ${PID} ") has been working in normal state for " $LIFE " seconds." + ((idx ++)) + sleep ${SLEEP_INTERVAL} + if [[ ${LIFE} -gt ${MAX_LIFETIME} ]]; then + kill -9 ${PID} + fi + done +} + +opt=$1 +case C"$opt" in + Cstart) + start + ;; + Cstop) + stop + ;; + Cterm) + term + ;; + Crestart) + term + start + ;; + Clist) + list + ;; + Cmonitor) + monitor + ;; + Ccrontab) + crontab + ;; + C*) + usage + ;; +esac + diff --git a/examples/echo/udp-echo/client/assembly/common/app.properties b/examples/echo/udp-echo/client/assembly/common/app.properties new file mode 100644 index 00000000..369cc336 --- /dev/null +++ b/examples/echo/udp-echo/client/assembly/common/app.properties @@ -0,0 +1,17 @@ +# getty application configure script +# ****************************************************** +# DESC : application environment variable +# AUTHOR : Alex Stocks +# VERSION : 1.0 +# LICENCE : Apache License 2.0 +# EMAIL : alexstocks@foxmail.com +# MOD : 2016-07-12 16:29 +# FILE : app.properties +# ****************************************************** + +export TARGET_EXEC_NAME="echo_client" +export BUILD_PACKAGE="app" + +export TARGET_CONF_FILE="conf/config.toml" +export TARGET_LOG_CONF_FILE="conf/log.xml" + diff --git a/examples/echo/udp-echo/client/assembly/common/build.sh b/examples/echo/udp-echo/client/assembly/common/build.sh new file mode 100644 index 00000000..00763725 --- /dev/null +++ b/examples/echo/udp-echo/client/assembly/common/build.sh @@ -0,0 +1,77 @@ +#!/usr/bin/env bash +# ****************************************************** +# DESC : build script +# AUTHOR : Alex Stocks +# VERSION : 1.0 +# LICENCE : Apache License 2.0 +# EMAIL : alexstocks@foxmail.com +# MOD : 2016-07-12 16:28 +# FILE : build.sh +# ****************************************************** + +rm -rf target/ + +PROJECT_HOME=`pwd` +TARGET_FOLDER=${PROJECT_HOME}/target/${GOOS} + +TARGET_SBIN_NAME=${TARGET_EXEC_NAME} +version=`cat app/version.go | grep Version | awk -F '=' '{print $2}' | awk -F '"' '{print $2}'` +if [[ ${GOOS} == "windows" ]]; then + TARGET_SBIN_NAME=${TARGET_SBIN_NAME}.exe +fi +TARGET_NAME=${TARGET_FOLDER}/${TARGET_SBIN_NAME} +if [[ $PROFILE == "dev" || $PROFILE == "test" ]]; then + # GFLAGS=-gcflags "-N -l" -race -x -v # -x会把go build的详细过程输出 + # GFLAGS=-gcflags "-N -l" -race -v + # GFLAGS="-gcflags \"-N -l\" -v" + cd ${BUILD_PACKAGE} && GOOS=$GOOS GOARCH=$GOARCH go build -gcflags "-N -l" -x -v -i -o ${TARGET_NAME} && cd - +else + # -s去掉符号表(然后panic时候的stack trace就没有任何文件名/行号信息了,这个等价于普通C/C++程序被strip的效果), + # -w去掉DWARF调试信息,得到的程序就不能用gdb调试了。-s和-w也可以分开使用,一般来说如果不打算用gdb调试, + # -w基本没啥损失。-s的损失就有点大了。 + cd ${BUILD_PACKAGE} && GOOS=$GOOS GOARCH=$GOARCH go build -ldflags "-w" -x -v -i -o ${TARGET_NAME} && cd - +fi + +TAR_NAME=${TARGET_EXEC_NAME}-${version}-`date "+%Y%m%d-%H%M"`-${PROFILE} + +mkdir -p ${TARGET_FOLDER}/${TAR_NAME} + +SBIN_DIR=${TARGET_FOLDER}/${TAR_NAME}/sbin +BIN_DIR=${TARGET_FOLDER}/${TAR_NAME} +CONF_DIR=${TARGET_FOLDER}/${TAR_NAME}/conf + +mkdir -p ${SBIN_DIR} +mkdir -p ${CONF_DIR} + +mv ${TARGET_NAME} ${SBIN_DIR} +cp -r assembly/bin ${BIN_DIR} +cd ${BIN_DIR}/bin/ && mv load.sh load_${TARGET_EXEC_NAME}.sh && cd - + +platform=$(uname) +# modify APPLICATION_NAME +if [ ${platform} == "Darwin" ]; then + sed -i "" "s~APPLICATION_NAME~${TARGET_EXEC_NAME}~g" ${BIN_DIR}/bin/* +else + sed -i "s~APPLICATION_NAME~${TARGET_EXEC_NAME}~g" ${BIN_DIR}/bin/* +fi + +# modify TARGET_CONF_FILE +if [ ${platform} == "Darwin" ]; then + sed -i "" "s~TARGET_CONF_FILE~${TARGET_CONF_FILE}~g" ${BIN_DIR}/bin/* +else + sed -i "s~TARGET_CONF_FILE~${TARGET_CONF_FILE}~g" ${BIN_DIR}/bin/* +fi + +# modify TARGET_LOG_CONF_FILE +if [ ${platform} == "Darwin" ]; then + sed -i "" "s~TARGET_LOG_CONF_FILE~${TARGET_LOG_CONF_FILE}~g" ${BIN_DIR}/bin/* +else + sed -i "s~TARGET_LOG_CONF_FILE~${TARGET_LOG_CONF_FILE}~g" ${BIN_DIR}/bin/* +fi + +cp -r profiles/${PROFILE}/* ${CONF_DIR} + +cd ${TARGET_FOLDER} + +tar czf ${TAR_NAME}.tar.gz ${TAR_NAME}/* + diff --git a/examples/echo/udp-echo/client/assembly/linux/dev.sh b/examples/echo/udp-echo/client/assembly/linux/dev.sh new file mode 100644 index 00000000..62c82fe6 --- /dev/null +++ b/examples/echo/udp-echo/client/assembly/linux/dev.sh @@ -0,0 +1,28 @@ +#!/usr/bin/env bash +# ****************************************************** +# DESC : build script for dev env +# AUTHOR : Alex Stocks +# VERSION : 1.0 +# LICENCE : Apache License 2.0 +# EMAIL : alexstocks@foxmail.com +# MOD : 2016-07-12 16:34 +# FILE : dev.sh +# ****************************************************** + + +set -e + +export GOOS=linux +export GOARCH=amd64 + +export PROFILE="dev" +export PROJECT_HOME=`pwd` + +if [ -f "${PROJECT_HOME}/assembly/common/app.properties" ]; then + . ${PROJECT_HOME}/assembly/common/app.properties +fi + + +if [ -f "${PROJECT_HOME}/assembly/common/build.sh" ]; then + sh ${PROJECT_HOME}/assembly/common/build.sh +fi diff --git a/examples/echo/udp-echo/client/assembly/linux/release.sh b/examples/echo/udp-echo/client/assembly/linux/release.sh new file mode 100755 index 00000000..2aeaf6d9 --- /dev/null +++ b/examples/echo/udp-echo/client/assembly/linux/release.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env bash +# ****************************************************** +# DESC : build script for release env +# AUTHOR : Alex Stocks +# VERSION : 1.0 +# LICENCE : Apache License 2.0 +# EMAIL : alexstocks@foxmail.com +# MOD : 2016-07-12 16:34 +# FILE : test.sh +# ****************************************************** + + +set -e + +export GOOS=linux +export GOARCH=amd64 + +export PROFILE="release" +export PROJECT_HOME=`pwd` + +if [ -f "${PROJECT_HOME}/assembly/common/app.properties" ]; then + . ${PROJECT_HOME}/assembly/common/app.properties +fi + +if [ -f "${PROJECT_HOME}/assembly/common/build.sh" ]; then + sh ${PROJECT_HOME}/assembly/common/build.sh +fi diff --git a/examples/echo/udp-echo/client/assembly/linux/test.sh b/examples/echo/udp-echo/client/assembly/linux/test.sh new file mode 100644 index 00000000..1bbbefd1 --- /dev/null +++ b/examples/echo/udp-echo/client/assembly/linux/test.sh @@ -0,0 +1,28 @@ +#!/usr/bin/env bash +# ****************************************************** +# DESC : build script for test env +# AUTHOR : Alex Stocks +# VERSION : 1.0 +# LICENCE : Apache License 2.0 +# EMAIL : alexstocks@foxmail.com +# MOD : 2016-07-12 16:34 +# FILE : test.sh +# ****************************************************** + + +set -e + +export GOOS=linux +export GOARCH=amd64 + +export PROFILE="test" +export PROJECT_HOME=`pwd` + +if [ -f "${PROJECT_HOME}/assembly/common/app.properties" ]; then + . ${PROJECT_HOME}/assembly/common/app.properties +fi + + +if [ -f "${PROJECT_HOME}/assembly/common/build.sh" ]; then + sh ${PROJECT_HOME}/assembly/common/build.sh +fi diff --git a/examples/echo/udp-echo/client/assembly/mac/dev.sh b/examples/echo/udp-echo/client/assembly/mac/dev.sh new file mode 100644 index 00000000..65e46867 --- /dev/null +++ b/examples/echo/udp-echo/client/assembly/mac/dev.sh @@ -0,0 +1,28 @@ +#!/usr/bin/env bash +# ****************************************************** +# DESC : build script for dev env +# AUTHOR : Alex Stocks +# VERSION : 1.0 +# LICENCE : Apache License 2.0 +# EMAIL : alexstocks@foxmail.com +# MOD : 2016-07-12 16:34 +# FILE : dev.sh +# ****************************************************** + + +set -e + +export GOOS=darwin +export GOARCH=amd64 + +export PROFILE="dev" +export PROJECT_HOME=`pwd` + +if [ -f "${PROJECT_HOME}/assembly/common/app.properties" ]; then + . ${PROJECT_HOME}/assembly/common/app.properties +fi + + +if [ -f "${PROJECT_HOME}/assembly/common/build.sh" ]; then + sh ${PROJECT_HOME}/assembly/common/build.sh +fi diff --git a/examples/echo/udp-echo/client/assembly/mac/release.sh b/examples/echo/udp-echo/client/assembly/mac/release.sh new file mode 100755 index 00000000..688288b3 --- /dev/null +++ b/examples/echo/udp-echo/client/assembly/mac/release.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env bash +# ****************************************************** +# DESC : build script for release env +# AUTHOR : Alex Stocks +# VERSION : 1.0 +# LICENCE : Apache License 2.0 +# EMAIL : alexstocks@foxmail.com +# MOD : 2016-07-12 16:34 +# FILE : test.sh +# ****************************************************** + + +set -e + +export GOOS=darwin +export GOARCH=amd64 + +export PROFILE="release" +export PROJECT_HOME=`pwd` + +if [ -f "${PROJECT_HOME}/assembly/common/app.properties" ]; then + . ${PROJECT_HOME}/assembly/common/app.properties +fi + +if [ -f "${PROJECT_HOME}/assembly/common/build.sh" ]; then + sh ${PROJECT_HOME}/assembly/common/build.sh +fi diff --git a/examples/echo/udp-echo/client/assembly/mac/test.sh b/examples/echo/udp-echo/client/assembly/mac/test.sh new file mode 100644 index 00000000..56d6c11e --- /dev/null +++ b/examples/echo/udp-echo/client/assembly/mac/test.sh @@ -0,0 +1,28 @@ +#!/usr/bin/env bash +# ****************************************************** +# DESC : build script for test env +# AUTHOR : Alex Stocks +# VERSION : 1.0 +# LICENCE : Apache License 2.0 +# EMAIL : alexstocks@foxmail.com +# MOD : 2016-07-12 16:34 +# FILE : test.sh +# ****************************************************** + + +set -e + +export GOOS=darwin +export GOARCH=amd64 + +export PROFILE="test" +export PROJECT_HOME=`pwd` + +if [ -f "${PROJECT_HOME}/assembly/common/app.properties" ]; then + . ${PROJECT_HOME}/assembly/common/app.properties +fi + + +if [ -f "${PROJECT_HOME}/assembly/common/build.sh" ]; then + sh ${PROJECT_HOME}/assembly/common/build.sh +fi diff --git a/examples/echo/udp-echo/client/assembly/windows/dev.sh b/examples/echo/udp-echo/client/assembly/windows/dev.sh new file mode 100644 index 00000000..94ed49ea --- /dev/null +++ b/examples/echo/udp-echo/client/assembly/windows/dev.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env bash +# ****************************************************** +# DESC : build script for dev env +# AUTHOR : Alex Stocks +# VERSION : 1.0 +# LICENCE : Apache License 2.0 +# EMAIL : alexstocks@foxmail.com +# MOD : 2016-07-12 16:34 +# FILE : dev.sh +# ****************************************************** + + +set -e + +export GOOS=windows +export GOARCH=amd64 + +export PROFILE="dev" +export PROJECT_HOME=`pwd` + +if [ -f "${PROJECT_HOME}/assembly/common/app.properties" ]; then + . ${PROJECT_HOME}/assembly/common/app.properties +fi + +if [ -f "${PROJECT_HOME}/assembly/common/build.sh" ]; then + sh ${PROJECT_HOME}/assembly/common/build.sh +fi diff --git a/examples/echo/udp-echo/client/assembly/windows/release.sh b/examples/echo/udp-echo/client/assembly/windows/release.sh new file mode 100755 index 00000000..f317720b --- /dev/null +++ b/examples/echo/udp-echo/client/assembly/windows/release.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env bash +# ****************************************************** +# DESC : build script for release env +# AUTHOR : Alex Stocks +# VERSION : 1.0 +# LICENCE : Apache License 2.0 +# EMAIL : alexstocks@foxmail.com +# MOD : 2016-07-12 16:34 +# FILE : test.sh +# ****************************************************** + + +set -e + +export GOOS=windows +export GOARCH=amd64 + +export PROFILE="release" +export PROJECT_HOME=`pwd` + +if [ -f "${PROJECT_HOME}/assembly/common/app.properties" ]; then + . ${PROJECT_HOME}/assembly/common/app.properties +fi + +if [ -f "${PROJECT_HOME}/assembly/common/build.sh" ]; then + sh ${PROJECT_HOME}/assembly/common/build.sh +fi diff --git a/examples/echo/udp-echo/client/assembly/windows/test.sh b/examples/echo/udp-echo/client/assembly/windows/test.sh new file mode 100644 index 00000000..7dd2bec5 --- /dev/null +++ b/examples/echo/udp-echo/client/assembly/windows/test.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env bash +# ****************************************************** +# DESC : build script for test env +# AUTHOR : Alex Stocks +# VERSION : 1.0 +# LICENCE : Apache License 2.0 +# EMAIL : alexstocks@foxmail.com +# MOD : 2016-07-12 16:34 +# FILE : test.sh +# ****************************************************** + + +set -e + +export GOOS=windows +export GOARCH=amd64 + +export PROFILE="test" +export PROJECT_HOME=`pwd` + +if [ -f "${PROJECT_HOME}/assembly/common/app.properties" ]; then + . ${PROJECT_HOME}/assembly/common/app.properties +fi + +if [ -f "${PROJECT_HOME}/assembly/common/build.sh" ]; then + sh ${PROJECT_HOME}/assembly/common/build.sh +fi diff --git a/examples/echo/udp-echo/client/profiles/dev/config.toml b/examples/echo/udp-echo/client/profiles/dev/config.toml new file mode 100644 index 00000000..bbff2100 --- /dev/null +++ b/examples/echo/udp-echo/client/profiles/dev/config.toml @@ -0,0 +1,44 @@ +# toml configure file +# toml中key的首字母可以小写,但是对应的golang中的struct成员首字母必须大写 + +AppName = "ECHO-CLIENT" + +# host +LocalHost = "127.0.0.1" + +# server +# ServerHost = "192.168.8.3" +ServerHost = "127.0.0.1" +ServerPort = 10000 +ProfilePort = 10080 + +# connection pool +# 连接池连接数目 +ConnectionNum = 2 + +# session +# client与server之间连接的心跳周期 +HeartbeatPeriod = "10s" +# client与server之间连接的超时时间 +SessionTimeout = "20s" + +# client +# client echo request string +EchoString = "Hello, getty!" +# 发送echo请求次数 +EchoTimes = 100000 + +# app fail fast +FailFastTimeout = "3s" + +# tcp +[GettySessionParam] + CompressEncoding = true + UdpRBufSize = 262144 + UdpWBufSize = 65536 + PkgWQSize = 256 + UdpReadTimeout = "10s" + UdpWriteTimeout = "5s" + WaitTimeout = "1s" + MaxMsgLen = 128 + SessionName = "echo-client" diff --git a/examples/echo/udp-echo/client/profiles/dev/log.xml b/examples/echo/udp-echo/client/profiles/dev/log.xml new file mode 100644 index 00000000..95ccaeed --- /dev/null +++ b/examples/echo/udp-echo/client/profiles/dev/log.xml @@ -0,0 +1,63 @@ + + + stdout + console + + DEBUG + + + debug_file + file + DEBUG + logs/debug.log + [%D %T] [%L] [%S] %M + true + 0M + 0K + true + + + info_file + file + INFO + logs/info.log + + [%D %T] [%L] [%S] %M + true + 0M + 0K + true + + + warn_file + file + WARNING + logs/warn.log + [%D %T] [%L] [%S] %M + true + 0M + 0K + true + + + error_file + file + ERROR + logs/error.log + [%D %T] [%L] [%S] %M + true + 0M + 0K + true + + diff --git a/examples/echo/udp-echo/client/profiles/release/config.toml b/examples/echo/udp-echo/client/profiles/release/config.toml new file mode 100644 index 00000000..bbff2100 --- /dev/null +++ b/examples/echo/udp-echo/client/profiles/release/config.toml @@ -0,0 +1,44 @@ +# toml configure file +# toml中key的首字母可以小写,但是对应的golang中的struct成员首字母必须大写 + +AppName = "ECHO-CLIENT" + +# host +LocalHost = "127.0.0.1" + +# server +# ServerHost = "192.168.8.3" +ServerHost = "127.0.0.1" +ServerPort = 10000 +ProfilePort = 10080 + +# connection pool +# 连接池连接数目 +ConnectionNum = 2 + +# session +# client与server之间连接的心跳周期 +HeartbeatPeriod = "10s" +# client与server之间连接的超时时间 +SessionTimeout = "20s" + +# client +# client echo request string +EchoString = "Hello, getty!" +# 发送echo请求次数 +EchoTimes = 100000 + +# app fail fast +FailFastTimeout = "3s" + +# tcp +[GettySessionParam] + CompressEncoding = true + UdpRBufSize = 262144 + UdpWBufSize = 65536 + PkgWQSize = 256 + UdpReadTimeout = "10s" + UdpWriteTimeout = "5s" + WaitTimeout = "1s" + MaxMsgLen = 128 + SessionName = "echo-client" diff --git a/examples/echo/udp-echo/client/profiles/release/log.xml b/examples/echo/udp-echo/client/profiles/release/log.xml new file mode 100644 index 00000000..95ccaeed --- /dev/null +++ b/examples/echo/udp-echo/client/profiles/release/log.xml @@ -0,0 +1,63 @@ + + + stdout + console + + DEBUG + + + debug_file + file + DEBUG + logs/debug.log + [%D %T] [%L] [%S] %M + true + 0M + 0K + true + + + info_file + file + INFO + logs/info.log + + [%D %T] [%L] [%S] %M + true + 0M + 0K + true + + + warn_file + file + WARNING + logs/warn.log + [%D %T] [%L] [%S] %M + true + 0M + 0K + true + + + error_file + file + ERROR + logs/error.log + [%D %T] [%L] [%S] %M + true + 0M + 0K + true + + diff --git a/examples/echo/udp-echo/client/profiles/test/config.toml b/examples/echo/udp-echo/client/profiles/test/config.toml new file mode 100644 index 00000000..bbff2100 --- /dev/null +++ b/examples/echo/udp-echo/client/profiles/test/config.toml @@ -0,0 +1,44 @@ +# toml configure file +# toml中key的首字母可以小写,但是对应的golang中的struct成员首字母必须大写 + +AppName = "ECHO-CLIENT" + +# host +LocalHost = "127.0.0.1" + +# server +# ServerHost = "192.168.8.3" +ServerHost = "127.0.0.1" +ServerPort = 10000 +ProfilePort = 10080 + +# connection pool +# 连接池连接数目 +ConnectionNum = 2 + +# session +# client与server之间连接的心跳周期 +HeartbeatPeriod = "10s" +# client与server之间连接的超时时间 +SessionTimeout = "20s" + +# client +# client echo request string +EchoString = "Hello, getty!" +# 发送echo请求次数 +EchoTimes = 100000 + +# app fail fast +FailFastTimeout = "3s" + +# tcp +[GettySessionParam] + CompressEncoding = true + UdpRBufSize = 262144 + UdpWBufSize = 65536 + PkgWQSize = 256 + UdpReadTimeout = "10s" + UdpWriteTimeout = "5s" + WaitTimeout = "1s" + MaxMsgLen = 128 + SessionName = "echo-client" diff --git a/examples/echo/udp-echo/client/profiles/test/log.xml b/examples/echo/udp-echo/client/profiles/test/log.xml new file mode 100644 index 00000000..95ccaeed --- /dev/null +++ b/examples/echo/udp-echo/client/profiles/test/log.xml @@ -0,0 +1,63 @@ + + + stdout + console + + DEBUG + + + debug_file + file + DEBUG + logs/debug.log + [%D %T] [%L] [%S] %M + true + 0M + 0K + true + + + info_file + file + INFO + logs/info.log + + [%D %T] [%L] [%S] %M + true + 0M + 0K + true + + + warn_file + file + WARNING + logs/warn.log + [%D %T] [%L] [%S] %M + true + 0M + 0K + true + + + error_file + file + ERROR + logs/error.log + [%D %T] [%L] [%S] %M + true + 0M + 0K + true + + diff --git a/examples/echo/udp-echo/server/app/config.go b/examples/echo/udp-echo/server/app/config.go new file mode 100644 index 00000000..8f2098d3 --- /dev/null +++ b/examples/echo/udp-echo/server/app/config.go @@ -0,0 +1,138 @@ +/* + * 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. + */ + +package main + +import ( + "fmt" + "os" + "path" + "time" +) + +import ( + log "github.com/AlexStocks/log4go" + config "github.com/koding/multiconfig" +) + +const ( + APP_CONF_FILE = "APP_CONF_FILE" + APP_LOG_CONF_FILE = "APP_LOG_CONF_FILE" +) + +var conf *Config + +type ( + GettySessionParam struct { + CompressEncoding bool `default:"false"` + UdpRBufSize int `default:"262144"` + UdpWBufSize int `default:"65536"` + PkgWQSize int `default:"1024"` + UdpReadTimeout string `default:"1s"` + udpReadTimeout time.Duration + UdpWriteTimeout string `default:"5s"` + udpWriteTimeout time.Duration + WaitTimeout string `default:"7s"` + waitTimeout time.Duration + MaxMsgLen int `default:"1024"` + SessionName string `default:"echo-server"` + } + + // Config holds supported types by the multiconfig package + Config struct { + // local address + AppName string `default:"echo-server"` + Host string `default:"127.0.0.1"` + Ports []string `default:["10000"]` + ProfilePort int `default:"10086"` + + // session + SessionTimeout string `default:"60s"` + sessionTimeout time.Duration + SessionNumber int `default:"1000"` + + // app + FailFastTimeout string `default:"5s"` + failFastTimeout time.Duration + + // session tcp parameters + GettySessionParam GettySessionParam `required:"true"` + } +) + +func initConf() { + var ( + err error + confFile string + ) + + // configure + confFile = os.Getenv(APP_CONF_FILE) + if confFile == "" { + panic(fmt.Sprintf("application configure file name is nil")) + return // I know it is of no usage. Just Err Protection. + } + if path.Ext(confFile) != ".toml" { + panic(fmt.Sprintf("application configure file name{%v} suffix must be .toml", confFile)) + return + } + conf = new(Config) + config.MustLoadWithPath(confFile, conf) + + conf.sessionTimeout, err = time.ParseDuration(conf.SessionTimeout) + if err != nil { + panic(fmt.Sprintf("time.ParseDuration(SessionTimeout{%#v}) = error{%v}", conf.SessionTimeout, err)) + return + } + conf.failFastTimeout, err = time.ParseDuration(conf.FailFastTimeout) + if err != nil { + panic(fmt.Sprintf("time.ParseDuration(FailFastTimeout{%#v}) = error{%v}", conf.FailFastTimeout, err)) + return + } + + conf.GettySessionParam.udpReadTimeout, err = time.ParseDuration(conf.GettySessionParam.UdpReadTimeout) + if err != nil { + panic(fmt.Sprintf("time.ParseDuration(UdpReadTimeout{%#v}) = error{%v}", conf.GettySessionParam.UdpReadTimeout, err)) + return + } + conf.GettySessionParam.udpWriteTimeout, err = time.ParseDuration(conf.GettySessionParam.UdpWriteTimeout) + if err != nil { + panic(fmt.Sprintf("time.ParseDuration(UdpWriteTimeout{%#v}) = error{%v}", conf.GettySessionParam.UdpWriteTimeout, err)) + return + } + conf.GettySessionParam.waitTimeout, err = time.ParseDuration(conf.GettySessionParam.WaitTimeout) + if err != nil { + panic(fmt.Sprintf("time.ParseDuration(WaitTimeout{%#v}) = error{%v}", conf.GettySessionParam.WaitTimeout, err)) + return + } + // gxlog.CInfo("config{%#v}\n", conf) + + // log + confFile = os.Getenv(APP_LOG_CONF_FILE) + if confFile == "" { + panic(fmt.Sprintf("log configure file name is nil")) + return + } + if path.Ext(confFile) != ".xml" { + panic(fmt.Sprintf("log configure file name{%v} suffix must be .xml", confFile)) + return + } + log.LoadConfiguration(confFile) + log.Info("config{%#v}", conf) + + return +} diff --git a/examples/echo/udp-echo/server/app/echo.go b/examples/echo/udp-echo/server/app/echo.go new file mode 100644 index 00000000..b7df0c85 --- /dev/null +++ b/examples/echo/udp-echo/server/app/echo.go @@ -0,0 +1,152 @@ +/* + * 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. + */ + +package main + +import ( + "bytes" + "encoding/binary" + "errors" + "fmt" + "unsafe" +) + +import ( + log "github.com/AlexStocks/log4go" +) + +//////////////////////////////////////////// +// echo command +//////////////////////////////////////////// + +type echoCommand uint32 + +const ( + heartbeatCmd = iota + echoCmd +) + +var echoCommandStrings = [...]string{ + "heartbeat", + "echo", +} + +func (c echoCommand) String() string { + return echoCommandStrings[c] +} + +//////////////////////////////////////////// +// EchoPkgHandler +//////////////////////////////////////////// + +const ( + echoPkgMagic = 0x20160905 + maxEchoStringLen = 0xff + + echoHeartbeatRequestString = "ping" + echoHeartbeatResponseString = "pong" +) + +var ( + ErrNotEnoughStream = errors.New("packet stream is not enough") + ErrTooLargePackage = errors.New("package length is exceed the echo package's legal maximum length.") + ErrIllegalMagic = errors.New("package magic is not right.") +) + +var echoPkgHeaderLen int + +func init() { + echoPkgHeaderLen = (int)((uint)(unsafe.Sizeof(EchoPkgHeader{}))) +} + +type EchoPkgHeader struct { + Magic uint32 + LogID uint32 // log id + + Sequence uint32 // request/response sequence + ServiceID uint32 // service id + + Command uint32 // operation command code + Code int32 // error code + + Len uint16 // body length + _ uint16 + _ int32 // reserved, maybe used as package md5 checksum +} + +type EchoPackage struct { + H EchoPkgHeader + B string +} + +func (p EchoPackage) String() string { + return fmt.Sprintf("log id:%d, sequence:%d, command:%s, echo string:%s", + p.H.LogID, p.H.Sequence, (echoCommand(p.H.Command)).String(), p.B) +} + +func (p EchoPackage) Marshal() (*bytes.Buffer, error) { + var ( + err error + buf *bytes.Buffer + ) + + buf = &bytes.Buffer{} + err = binary.Write(buf, binary.LittleEndian, p.H) + if err != nil { + return nil, err + } + buf.WriteByte((byte)(len(p.B))) + buf.WriteString(p.B) + + return buf, nil +} + +func (p *EchoPackage) Unmarshal(buf *bytes.Buffer) (int, error) { + var ( + err error + len byte + ) + + if buf.Len() < echoPkgHeaderLen { + return 0, ErrNotEnoughStream + } + + // header + err = binary.Read(buf, binary.LittleEndian, &(p.H)) + if err != nil { + return 0, err + } + if p.H.Magic != echoPkgMagic { + log.Error("@p.H.Magic{%x}, right magic{%x}", p.H.Magic, echoPkgMagic) + return 0, ErrIllegalMagic + } + if buf.Len() < (int)(p.H.Len) { + return 0, ErrNotEnoughStream + } + // 防止恶意客户端把这个字段设置过大导致服务端死等或者服务端在准备对应的缓冲区时内存崩溃 + if maxEchoStringLen < p.H.Len-1 { + return 0, ErrTooLargePackage + } + + len, err = buf.ReadByte() + if err != nil { + return 0, nil + } + p.B = (string)(buf.Next((int)(len))) + + return (int)(p.H.Len) + echoPkgHeaderLen, nil +} diff --git a/examples/echo/udp-echo/server/app/handler.go b/examples/echo/udp-echo/server/app/handler.go new file mode 100644 index 00000000..04847e34 --- /dev/null +++ b/examples/echo/udp-echo/server/app/handler.go @@ -0,0 +1,199 @@ +/* + * 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. + */ + +package main + +import ( + "errors" + "fmt" + "sync" + "time" +) + +import ( + log "github.com/AlexStocks/log4go" + getty "github.com/apache/dubbo-getty" +) + +const ( + WritePkgTimeout = 1e8 + WritePkgASAP = 0e9 +) + +var ( + errTooManySessions = errors.New("Too many echo sessions!") + hbHandler = &HeartbeatHandler{} + msgHandler = &MessageHandler{} + echoMsgHandler = newEchoMessageHandler() +) + +type PackageHandler interface { + Handle(getty.Session, getty.UDPContext) error +} + +//////////////////////////////////////////// +// heartbeat handler +//////////////////////////////////////////// + +type HeartbeatHandler struct{} + +func (h *HeartbeatHandler) Handle(session getty.Session, ctx getty.UDPContext) error { + var ( + ok bool + pkg *EchoPackage + rspPkg EchoPackage + ) + + log.Debug("get echo heartbeat udp context{%#v}", ctx) + if pkg, ok = ctx.Pkg.(*EchoPackage); !ok { + return fmt.Errorf("illegal @ctx.Pkg:%#v", ctx.Pkg) + } + + rspPkg.H = pkg.H + rspPkg.B = echoHeartbeatResponseString + rspPkg.H.Len = uint16(len(rspPkg.B) + 1) + + // return session.WritePkg(getty.UDPContext{Pkg: &rspPkg, PeerAddr: ctx.PeerAddr}, WritePkgTimeout) + _, _, err := session.WritePkg(getty.UDPContext{Pkg: &rspPkg, PeerAddr: ctx.PeerAddr}, WritePkgASAP) + if err != nil { + log.Warn("session.WritePkg(session{%s}, pkg{%s}) = error{%v}", session.Stat(), pkg, err) + session.Close() + } + return err +} + +//////////////////////////////////////////// +// message handler +//////////////////////////////////////////// + +type MessageHandler struct{} + +func (h *MessageHandler) Handle(session getty.Session, ctx getty.UDPContext) error { + log.Debug("get echo ctx{%#v}", ctx) + // write echo message handle logic here. + // return session.WritePkg(ctx, WritePkgTimeout) + _, _, err := session.WritePkg(ctx, WritePkgASAP) + return err +} + +//////////////////////////////////////////// +// EchoMessageHandler +//////////////////////////////////////////// + +type clientEchoSession struct { + session getty.Session + reqNum int32 +} + +type EchoMessageHandler struct { + handlers map[uint32]PackageHandler + + rwlock sync.RWMutex + sessionMap map[getty.Session]*clientEchoSession +} + +func newEchoMessageHandler() *EchoMessageHandler { + handlers := make(map[uint32]PackageHandler) + handlers[heartbeatCmd] = hbHandler + handlers[echoCmd] = msgHandler + + return &EchoMessageHandler{sessionMap: make(map[getty.Session]*clientEchoSession), handlers: handlers} +} + +func (h *EchoMessageHandler) OnOpen(session getty.Session) error { + var err error + + h.rwlock.RLock() + if conf.SessionNumber <= len(h.sessionMap) { + err = errTooManySessions + } + h.rwlock.RUnlock() + if err != nil { + return err + } + + log.Info("got session:%s", session.Stat()) + h.rwlock.Lock() + h.sessionMap[session] = &clientEchoSession{session: session} + h.rwlock.Unlock() + return nil +} + +func (h *EchoMessageHandler) OnError(session getty.Session, err error) { + log.Info("session{%s} got error{%v}, will be closed.", session.Stat(), err) + h.rwlock.Lock() + delete(h.sessionMap, session) + h.rwlock.Unlock() +} + +func (h *EchoMessageHandler) OnClose(session getty.Session) { + log.Info("session{%s} is closing......", session.Stat()) + h.rwlock.Lock() + delete(h.sessionMap, session) + h.rwlock.Unlock() +} + +func (h *EchoMessageHandler) OnMessage(session getty.Session, udpCtx interface{}) { + ctx, ok := udpCtx.(getty.UDPContext) + if !ok { + log.Error("illegal UDPContext{%#v}", udpCtx) + return + } + + p, ok := ctx.Pkg.(*EchoPackage) + if !ok { + log.Error("illegal pkg{%#v}", ctx.Pkg) + return + } + + handler, ok := h.handlers[p.H.Command] + if !ok { + log.Error("illegal command{%d}", p.H.Command) + return + } + err := handler.Handle(session, ctx) + if err != nil { + h.rwlock.Lock() + if _, ok := h.sessionMap[session]; ok { + h.sessionMap[session].reqNum++ + } + h.rwlock.Unlock() + } +} + +func (h *EchoMessageHandler) OnCron(session getty.Session) { + + // flag bool + var active time.Time + h.rwlock.RLock() + if _, ok := h.sessionMap[session]; ok { + active = session.GetActive() + if conf.sessionTimeout.Nanoseconds() < time.Since(active).Nanoseconds() { + // flag = true + log.Error("session{%s} timeout{%s}, reqNum{%d}", + session.Stat(), time.Since(active).String(), h.sessionMap[session].reqNum) + } + } + h.rwlock.RUnlock() + // udp session是根据本地udp socket fd生成的,如果关闭则连同socket也一同关闭了 + //if flag { + // h.rwlock.Lock() + // delete(h.sessionMap, session) + // h.rwlock.Unlock() + // session.Close() + //} +} diff --git a/examples/echo/udp-echo/server/app/readwriter.go b/examples/echo/udp-echo/server/app/readwriter.go new file mode 100644 index 00000000..18c05e8a --- /dev/null +++ b/examples/echo/udp-echo/server/app/readwriter.go @@ -0,0 +1,92 @@ +/* + * 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. + */ + +package main + +import ( + "bytes" + "errors" + "fmt" + "time" +) + +import ( + log "github.com/AlexStocks/log4go" + getty "github.com/apache/dubbo-getty" +) + +var echoPkgHandler = NewEchoPackageHandler() + +type EchoPackageHandler struct{} + +func NewEchoPackageHandler() *EchoPackageHandler { + return &EchoPackageHandler{} +} + +func (h *EchoPackageHandler) Read(ss getty.Session, data []byte) (interface{}, int, error) { + var ( + err error + len int + pkg EchoPackage + buf *bytes.Buffer + ) + + buf = bytes.NewBuffer(data) + len, err = pkg.Unmarshal(buf) + if err != nil { + if err == ErrNotEnoughStream { + return nil, 0, nil + } + + return nil, 0, err + } + + return &pkg, len, nil +} + +func (h *EchoPackageHandler) Write(ss getty.Session, udpCtx interface{}) ([]byte, error) { + var ( + ok bool + err error + startTime time.Time + echoPkg *EchoPackage + buf *bytes.Buffer + ctx getty.UDPContext + ) + + ctx, ok = udpCtx.(getty.UDPContext) + if !ok { + log.Error("illegal UDPContext{%#v}", udpCtx) + return nil, fmt.Errorf("illegal @udpCtx{%#v}", udpCtx) + } + + startTime = time.Now() + if echoPkg, ok = ctx.Pkg.(*EchoPackage); !ok { + log.Error("illegal pkg:%+v, addr:%s\n", ctx.Pkg, ctx.PeerAddr) + return nil, errors.New("invalid echo package!") + } + + buf, err = echoPkg.Marshal() + if err != nil { + log.Warn("binary.Write(echoPkg{%#v}) = err{%#v}", echoPkg, err) + return nil, err + } + + log.Debug("WriteEchoPkgTimeMs = %s", time.Since(startTime).String()) + + return buf.Bytes(), nil +} diff --git a/examples/echo/udp-echo/server/app/server.go b/examples/echo/udp-echo/server/app/server.go new file mode 100644 index 00000000..12e254a0 --- /dev/null +++ b/examples/echo/udp-echo/server/app/server.go @@ -0,0 +1,178 @@ +/* + * 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. + */ + +package main + +import ( + // "flag" + "fmt" + "net" + "net/http" + _ "net/http/pprof" + "os" + "os/signal" + + // "strings" + "syscall" + "time" +) + +import ( + gxlog "github.com/AlexStocks/goext/log" + gxnet "github.com/AlexStocks/goext/net" + log "github.com/AlexStocks/log4go" + getty "github.com/apache/dubbo-getty" +) + +const ( + pprofPath = "/debug/pprof/" +) + +var ( +// host = flag.String("host", "127.0.0.1", "local host address that server app will use") +// ports = flag.String("ports", "12345,12346,12347", "local host port list that the server app will bind") +) + +var serverList []getty.Server + +func main() { + // flag.Parse() + // if *host == "" || *ports == "" { + // panic(fmt.Sprintf("Please intput local host ip or port lists")) + // } + + initConf() + + initProfiling() + + initServer() + gxlog.CInfo("%s starts successfull! its version=%s, its listen ends=%s:%s\n", + conf.AppName, conf.Host, conf.Ports) + log.Info("%s starts successfull! its version=%s, its listen ends=%s:%s\n", + conf.AppName, conf.Host, conf.Ports) + + initSignal() +} + +func initProfiling() { + var addr string + + // addr = *host + ":" + "10000" + addr = gxnet.HostAddress(conf.Host, conf.ProfilePort) + log.Info("App Profiling startup on address{%v}", addr+pprofPath) + go func() { + log.Info(http.ListenAndServe(addr, nil)) + }() +} + +func newSession(session getty.Session) error { + var ( + ok bool + udpConn *net.UDPConn + ) + + if conf.GettySessionParam.CompressEncoding { + session.SetCompressType(getty.CompressZip) + } + + gxlog.CInfo("session:%#v", session) + if udpConn, ok = session.Conn().(*net.UDPConn); !ok { + panic(fmt.Sprintf("%s, session.conn{%#v} is not udp connection\n", session.Stat(), session.Conn())) + } + + udpConn.SetReadBuffer(conf.GettySessionParam.UdpRBufSize) + udpConn.SetWriteBuffer(conf.GettySessionParam.UdpWBufSize) + + session.SetName(conf.GettySessionParam.SessionName) + session.SetMaxMsgLen(conf.GettySessionParam.MaxMsgLen) + session.SetPkgHandler(echoPkgHandler) + session.SetEventListener(echoMsgHandler) + session.SetReadTimeout(conf.GettySessionParam.udpReadTimeout) + session.SetWriteTimeout(conf.GettySessionParam.udpWriteTimeout) + session.SetCronPeriod((int)(conf.sessionTimeout.Nanoseconds() / 1e6)) + session.SetWaitTime(conf.GettySessionParam.waitTimeout) + log.Debug("app accepts new session:%s\n", session.Stat()) + + return nil +} + +func initServer() { + var ( + addr string + portList []string + server getty.Server + ) + + // if *host == "" { + // panic("host can not be nil") + // } + // if *ports == "" { + // panic("ports can not be nil") + // } + + // portList = strings.Split(*ports, ",") + + portList = conf.Ports + if len(portList) == 0 { + panic("portList is nil") + } + for _, port := range portList { + addr = gxnet.HostAddress2(conf.Host, port) + server = getty.NewUDPEndPoint( + getty.WithLocalAddress(addr), + ) + // run server + server.RunEventLoop(newSession) + log.Debug("server bind addr{%s} ok!", addr) + serverList = append(serverList, server) + } +} + +func uninitServer() { + for _, server := range serverList { + server.Close() + } +} + +func initSignal() { + // signal.Notify的ch信道是阻塞的(signal.Notify不会阻塞发送信号), 需要设置缓冲 + signals := make(chan os.Signal, 1) + // It is not possible to block SIGKILL or syscall.SIGSTOP + signal.Notify(signals, os.Interrupt, os.Kill, syscall.SIGHUP, syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT) + for { + sig := <-signals + log.Info("get signal %s", sig.String()) + switch sig { + case syscall.SIGHUP: + // reload() + default: + go time.AfterFunc(conf.failFastTimeout, func() { + // log.Warn("app exit now by force...") + // os.Exit(1) + log.Exit("app exit now by force...") + log.Close() + }) + + // 要么fastFailTimeout时间内执行完毕下面的逻辑然后程序退出,要么执行上面的超时函数程序强行退出 + uninitServer() + // fmt.Println("app exit now...") + log.Exit("app exit now...") + log.Close() + return + } + } +} diff --git a/examples/echo/udp-echo/server/assembly/bin/load.sh b/examples/echo/udp-echo/server/assembly/bin/load.sh new file mode 100644 index 00000000..1d038cc0 --- /dev/null +++ b/examples/echo/udp-echo/server/assembly/bin/load.sh @@ -0,0 +1,186 @@ +#!/usr/bin/env bash +# ****************************************************** +# DESC : getty app devops script +# AUTHOR : Alex Stocks +# VERSION : 1.0 +# LICENCE : LGPL V3 +# EMAIL : alexstocks@foxmail.com +# MOD : 2016-05-13 02:01 +# FILE : load.sh +# ****************************************************** + +APP_NAME="APPLICATION_NAME" +APP_ARGS="" +SLEEP_INTERVAL=5 +MAX_LIFETIME=4000 + +PROJECT_HOME="" +OS_NAME=`uname` +if [[ ${OS_NAME} != "Windows" ]]; then + PROJECT_HOME=`pwd` + PROJECT_HOME=${PROJECT_HOME}"/" +else + APP_NAME="APPLICATION_NAME.exe" +fi + +export APP_CONF_FILE=${PROJECT_HOME}"TARGET_CONF_FILE" +export APP_LOG_CONF_FILE=${PROJECT_HOME}"TARGET_LOG_CONF_FILE" +# export GOTRACEBACK=system +# export GODEBUG=gctrace=1 + +usage() { + echo "Usage: $0 start" + echo " $0 stop" + echo " $0 term" + echo " $0 restart" + echo " $0 list" + echo " $0 monitor" + echo " $0 crontab" + exit +} + +start() { + APP_LOG_PATH=${PROJECT_HOME}"logs/" + mkdir -p ${APP_LOG_PATH} + APP_BIN=${PROJECT_HOME}sbin/${APP_NAME} + chmod u+x ${APP_BIN} + # CMD="nohup ${APP_BIN} ${APP_ARGS} >>${APP_NAME}.nohup.out 2>&1 &" + CMD="${APP_BIN}" + eval ${CMD} + PID=`ps aux | grep -w ${APP_NAME} | grep -v grep | awk '{print $2}'` + if [[ ${OS_NAME} != "Linux" && ${OS_NAME} != "Darwin" ]]; then + PID=`ps aux | grep -w ${APP_NAME} | grep -v grep | awk '{print $1}'` + fi + CUR=`date +%FT%T` + if [ "${PID}" != "" ]; then + for p in ${PID} + do + echo "start ${APP_NAME} ( pid =" ${p} ") at " ${CUR} + done + fi +} + +stop() { + PID=`ps aux | grep -w ${APP_NAME} | grep -v grep | awk '{print $2}'` + if [[ ${OS_NAME} != "Linux" && ${OS_NAME} != "Darwin" ]]; then + PID=`ps aux | grep -w ${APP_NAME} | grep -v grep | awk '{print $1}'` + fi + if [ "${PID}" != "" ]; + then + for ps in ${PID} + do + echo "kill -SIGINT ${APP_NAME} ( pid =" ${ps} ")" + kill -2 ${ps} + done + fi +} + + +term() { + PID=`ps aux | grep -w ${APP_NAME} | grep -v grep | awk '{print $2}'` + if [[ ${OS_NAME} != "Linux" && ${OS_NAME} != "Darwin" ]]; then + PID=`ps aux | grep -w ${APP_NAME} | grep -v grep | awk '{print $1}'` + fi + if [ "${PID}" != "" ]; + then + for ps in ${PID} + do + echo "kill -9 ${APP_NAME} ( pid =" ${ps} ")" + kill -9 ${ps} + done + fi +} + +list() { + PID=`ps aux | grep -w ${APP_NAME} | grep -v grep | awk '{printf("%s,%s,%s,%s\n", $1, $2, $9, $10)}'` + if [[ ${OS_NAME} != "Linux" && ${OS_NAME} != "Darwin" ]]; then + PID=`ps aux | grep -w ${APP_NAME} | grep -v grep | awk '{printf("%s,%s,%s,%s,%s\n", $1, $4, $6, $7, $8)}'` + fi + + if [ "${PID}" != "" ]; then + echo "list ${APP_NAME}" + + if [[ ${OS_NAME} == "Linux" || ${OS_NAME} == "Darwin" ]]; then + echo "index: user, pid, start, duration" + else + echo "index: PID, WINPID, UID, STIME, COMMAND" + fi + idx=0 + for ps in ${PID} + do + echo "${idx}: ${ps}" + ((idx ++)) + done + fi +} + +monitor() { + idx=0 + while true; do + PID=`ps aux | grep -w ${APP_NAME} | grep -v grep | awk '{print $2}'` + if [[ ${OS_NAME} != "Linux" && ${OS_NAME} != "Darwin" ]]; then + PID=`ps aux | grep -w ${APP_NAME} | grep -v grep | awk '{print $1}'` + fi + if [[ "${PID}" == "" ]]; then + start + idx=0 + fi + + ((LIFE=idx*${SLEEP_INTERVAL})) + echo "${APP_NAME} ( pid = " ${PID} ") has been working in normal state for " $LIFE " seconds." + ((idx ++)) + sleep ${SLEEP_INTERVAL} + done +} + +crontab() { + idx=0 + while true; do + PID=`ps aux | grep -w ${APP_NAME} | grep -v grep | awk '{print $2}'` + if [[ ${OS_NAME} != "Linux" && ${OS_NAME} != "Darwin" ]]; then + PID=`ps aux | grep -w ${APP_NAME} | grep -v grep | awk '{print $1}'` + fi + if [[ "${PID}" == "" ]]; then + start + idx=0 + fi + + ((LIFE=idx*${SLEEP_INTERVAL})) + echo "${APP_NAME} ( pid = " ${PID} ") has been working in normal state for " $LIFE " seconds." + ((idx ++)) + sleep ${SLEEP_INTERVAL} + if [[ ${LIFE} -gt ${MAX_LIFETIME} ]]; then + kill -9 ${PID} + fi + done +} + +opt=$1 +case C"$opt" in + Cstart) + start + ;; + Cstop) + stop + ;; + Cterm) + term + ;; + Crestart) + term + start + ;; + Clist) + list + ;; + Cmonitor) + monitor + ;; + Ccrontab) + crontab + ;; + C*) + usage + ;; +esac + diff --git a/examples/echo/udp-echo/server/assembly/common/app.properties b/examples/echo/udp-echo/server/assembly/common/app.properties new file mode 100644 index 00000000..ee33f677 --- /dev/null +++ b/examples/echo/udp-echo/server/assembly/common/app.properties @@ -0,0 +1,17 @@ +# getty application configure script +# ****************************************************** +# DESC : application environment variable +# AUTHOR : Alex Stocks +# VERSION : 1.0 +# LICENCE : Apache License 2.0 +# EMAIL : alexstocks@foxmail.com +# MOD : 2016-07-12 16:29 +# FILE : app.properties +# ****************************************************** + +export TARGET_EXEC_NAME="echo_server" +export BUILD_PACKAGE="app" + +export TARGET_CONF_FILE="conf/config.toml" +export TARGET_LOG_CONF_FILE="conf/log.xml" + diff --git a/examples/echo/udp-echo/server/assembly/common/build.sh b/examples/echo/udp-echo/server/assembly/common/build.sh new file mode 100644 index 00000000..00763725 --- /dev/null +++ b/examples/echo/udp-echo/server/assembly/common/build.sh @@ -0,0 +1,77 @@ +#!/usr/bin/env bash +# ****************************************************** +# DESC : build script +# AUTHOR : Alex Stocks +# VERSION : 1.0 +# LICENCE : Apache License 2.0 +# EMAIL : alexstocks@foxmail.com +# MOD : 2016-07-12 16:28 +# FILE : build.sh +# ****************************************************** + +rm -rf target/ + +PROJECT_HOME=`pwd` +TARGET_FOLDER=${PROJECT_HOME}/target/${GOOS} + +TARGET_SBIN_NAME=${TARGET_EXEC_NAME} +version=`cat app/version.go | grep Version | awk -F '=' '{print $2}' | awk -F '"' '{print $2}'` +if [[ ${GOOS} == "windows" ]]; then + TARGET_SBIN_NAME=${TARGET_SBIN_NAME}.exe +fi +TARGET_NAME=${TARGET_FOLDER}/${TARGET_SBIN_NAME} +if [[ $PROFILE == "dev" || $PROFILE == "test" ]]; then + # GFLAGS=-gcflags "-N -l" -race -x -v # -x会把go build的详细过程输出 + # GFLAGS=-gcflags "-N -l" -race -v + # GFLAGS="-gcflags \"-N -l\" -v" + cd ${BUILD_PACKAGE} && GOOS=$GOOS GOARCH=$GOARCH go build -gcflags "-N -l" -x -v -i -o ${TARGET_NAME} && cd - +else + # -s去掉符号表(然后panic时候的stack trace就没有任何文件名/行号信息了,这个等价于普通C/C++程序被strip的效果), + # -w去掉DWARF调试信息,得到的程序就不能用gdb调试了。-s和-w也可以分开使用,一般来说如果不打算用gdb调试, + # -w基本没啥损失。-s的损失就有点大了。 + cd ${BUILD_PACKAGE} && GOOS=$GOOS GOARCH=$GOARCH go build -ldflags "-w" -x -v -i -o ${TARGET_NAME} && cd - +fi + +TAR_NAME=${TARGET_EXEC_NAME}-${version}-`date "+%Y%m%d-%H%M"`-${PROFILE} + +mkdir -p ${TARGET_FOLDER}/${TAR_NAME} + +SBIN_DIR=${TARGET_FOLDER}/${TAR_NAME}/sbin +BIN_DIR=${TARGET_FOLDER}/${TAR_NAME} +CONF_DIR=${TARGET_FOLDER}/${TAR_NAME}/conf + +mkdir -p ${SBIN_DIR} +mkdir -p ${CONF_DIR} + +mv ${TARGET_NAME} ${SBIN_DIR} +cp -r assembly/bin ${BIN_DIR} +cd ${BIN_DIR}/bin/ && mv load.sh load_${TARGET_EXEC_NAME}.sh && cd - + +platform=$(uname) +# modify APPLICATION_NAME +if [ ${platform} == "Darwin" ]; then + sed -i "" "s~APPLICATION_NAME~${TARGET_EXEC_NAME}~g" ${BIN_DIR}/bin/* +else + sed -i "s~APPLICATION_NAME~${TARGET_EXEC_NAME}~g" ${BIN_DIR}/bin/* +fi + +# modify TARGET_CONF_FILE +if [ ${platform} == "Darwin" ]; then + sed -i "" "s~TARGET_CONF_FILE~${TARGET_CONF_FILE}~g" ${BIN_DIR}/bin/* +else + sed -i "s~TARGET_CONF_FILE~${TARGET_CONF_FILE}~g" ${BIN_DIR}/bin/* +fi + +# modify TARGET_LOG_CONF_FILE +if [ ${platform} == "Darwin" ]; then + sed -i "" "s~TARGET_LOG_CONF_FILE~${TARGET_LOG_CONF_FILE}~g" ${BIN_DIR}/bin/* +else + sed -i "s~TARGET_LOG_CONF_FILE~${TARGET_LOG_CONF_FILE}~g" ${BIN_DIR}/bin/* +fi + +cp -r profiles/${PROFILE}/* ${CONF_DIR} + +cd ${TARGET_FOLDER} + +tar czf ${TAR_NAME}.tar.gz ${TAR_NAME}/* + diff --git a/examples/echo/udp-echo/server/assembly/linux/dev.sh b/examples/echo/udp-echo/server/assembly/linux/dev.sh new file mode 100644 index 00000000..62c82fe6 --- /dev/null +++ b/examples/echo/udp-echo/server/assembly/linux/dev.sh @@ -0,0 +1,28 @@ +#!/usr/bin/env bash +# ****************************************************** +# DESC : build script for dev env +# AUTHOR : Alex Stocks +# VERSION : 1.0 +# LICENCE : Apache License 2.0 +# EMAIL : alexstocks@foxmail.com +# MOD : 2016-07-12 16:34 +# FILE : dev.sh +# ****************************************************** + + +set -e + +export GOOS=linux +export GOARCH=amd64 + +export PROFILE="dev" +export PROJECT_HOME=`pwd` + +if [ -f "${PROJECT_HOME}/assembly/common/app.properties" ]; then + . ${PROJECT_HOME}/assembly/common/app.properties +fi + + +if [ -f "${PROJECT_HOME}/assembly/common/build.sh" ]; then + sh ${PROJECT_HOME}/assembly/common/build.sh +fi diff --git a/examples/echo/udp-echo/server/assembly/linux/release.sh b/examples/echo/udp-echo/server/assembly/linux/release.sh new file mode 100755 index 00000000..2aeaf6d9 --- /dev/null +++ b/examples/echo/udp-echo/server/assembly/linux/release.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env bash +# ****************************************************** +# DESC : build script for release env +# AUTHOR : Alex Stocks +# VERSION : 1.0 +# LICENCE : Apache License 2.0 +# EMAIL : alexstocks@foxmail.com +# MOD : 2016-07-12 16:34 +# FILE : test.sh +# ****************************************************** + + +set -e + +export GOOS=linux +export GOARCH=amd64 + +export PROFILE="release" +export PROJECT_HOME=`pwd` + +if [ -f "${PROJECT_HOME}/assembly/common/app.properties" ]; then + . ${PROJECT_HOME}/assembly/common/app.properties +fi + +if [ -f "${PROJECT_HOME}/assembly/common/build.sh" ]; then + sh ${PROJECT_HOME}/assembly/common/build.sh +fi diff --git a/examples/echo/udp-echo/server/assembly/linux/test.sh b/examples/echo/udp-echo/server/assembly/linux/test.sh new file mode 100644 index 00000000..1bbbefd1 --- /dev/null +++ b/examples/echo/udp-echo/server/assembly/linux/test.sh @@ -0,0 +1,28 @@ +#!/usr/bin/env bash +# ****************************************************** +# DESC : build script for test env +# AUTHOR : Alex Stocks +# VERSION : 1.0 +# LICENCE : Apache License 2.0 +# EMAIL : alexstocks@foxmail.com +# MOD : 2016-07-12 16:34 +# FILE : test.sh +# ****************************************************** + + +set -e + +export GOOS=linux +export GOARCH=amd64 + +export PROFILE="test" +export PROJECT_HOME=`pwd` + +if [ -f "${PROJECT_HOME}/assembly/common/app.properties" ]; then + . ${PROJECT_HOME}/assembly/common/app.properties +fi + + +if [ -f "${PROJECT_HOME}/assembly/common/build.sh" ]; then + sh ${PROJECT_HOME}/assembly/common/build.sh +fi diff --git a/examples/echo/udp-echo/server/assembly/mac/dev.sh b/examples/echo/udp-echo/server/assembly/mac/dev.sh new file mode 100644 index 00000000..65e46867 --- /dev/null +++ b/examples/echo/udp-echo/server/assembly/mac/dev.sh @@ -0,0 +1,28 @@ +#!/usr/bin/env bash +# ****************************************************** +# DESC : build script for dev env +# AUTHOR : Alex Stocks +# VERSION : 1.0 +# LICENCE : Apache License 2.0 +# EMAIL : alexstocks@foxmail.com +# MOD : 2016-07-12 16:34 +# FILE : dev.sh +# ****************************************************** + + +set -e + +export GOOS=darwin +export GOARCH=amd64 + +export PROFILE="dev" +export PROJECT_HOME=`pwd` + +if [ -f "${PROJECT_HOME}/assembly/common/app.properties" ]; then + . ${PROJECT_HOME}/assembly/common/app.properties +fi + + +if [ -f "${PROJECT_HOME}/assembly/common/build.sh" ]; then + sh ${PROJECT_HOME}/assembly/common/build.sh +fi diff --git a/examples/echo/udp-echo/server/assembly/mac/release.sh b/examples/echo/udp-echo/server/assembly/mac/release.sh new file mode 100755 index 00000000..688288b3 --- /dev/null +++ b/examples/echo/udp-echo/server/assembly/mac/release.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env bash +# ****************************************************** +# DESC : build script for release env +# AUTHOR : Alex Stocks +# VERSION : 1.0 +# LICENCE : Apache License 2.0 +# EMAIL : alexstocks@foxmail.com +# MOD : 2016-07-12 16:34 +# FILE : test.sh +# ****************************************************** + + +set -e + +export GOOS=darwin +export GOARCH=amd64 + +export PROFILE="release" +export PROJECT_HOME=`pwd` + +if [ -f "${PROJECT_HOME}/assembly/common/app.properties" ]; then + . ${PROJECT_HOME}/assembly/common/app.properties +fi + +if [ -f "${PROJECT_HOME}/assembly/common/build.sh" ]; then + sh ${PROJECT_HOME}/assembly/common/build.sh +fi diff --git a/examples/echo/udp-echo/server/assembly/mac/test.sh b/examples/echo/udp-echo/server/assembly/mac/test.sh new file mode 100644 index 00000000..56d6c11e --- /dev/null +++ b/examples/echo/udp-echo/server/assembly/mac/test.sh @@ -0,0 +1,28 @@ +#!/usr/bin/env bash +# ****************************************************** +# DESC : build script for test env +# AUTHOR : Alex Stocks +# VERSION : 1.0 +# LICENCE : Apache License 2.0 +# EMAIL : alexstocks@foxmail.com +# MOD : 2016-07-12 16:34 +# FILE : test.sh +# ****************************************************** + + +set -e + +export GOOS=darwin +export GOARCH=amd64 + +export PROFILE="test" +export PROJECT_HOME=`pwd` + +if [ -f "${PROJECT_HOME}/assembly/common/app.properties" ]; then + . ${PROJECT_HOME}/assembly/common/app.properties +fi + + +if [ -f "${PROJECT_HOME}/assembly/common/build.sh" ]; then + sh ${PROJECT_HOME}/assembly/common/build.sh +fi diff --git a/examples/echo/udp-echo/server/assembly/windows/dev.sh b/examples/echo/udp-echo/server/assembly/windows/dev.sh new file mode 100644 index 00000000..94ed49ea --- /dev/null +++ b/examples/echo/udp-echo/server/assembly/windows/dev.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env bash +# ****************************************************** +# DESC : build script for dev env +# AUTHOR : Alex Stocks +# VERSION : 1.0 +# LICENCE : Apache License 2.0 +# EMAIL : alexstocks@foxmail.com +# MOD : 2016-07-12 16:34 +# FILE : dev.sh +# ****************************************************** + + +set -e + +export GOOS=windows +export GOARCH=amd64 + +export PROFILE="dev" +export PROJECT_HOME=`pwd` + +if [ -f "${PROJECT_HOME}/assembly/common/app.properties" ]; then + . ${PROJECT_HOME}/assembly/common/app.properties +fi + +if [ -f "${PROJECT_HOME}/assembly/common/build.sh" ]; then + sh ${PROJECT_HOME}/assembly/common/build.sh +fi diff --git a/examples/echo/udp-echo/server/assembly/windows/release.sh b/examples/echo/udp-echo/server/assembly/windows/release.sh new file mode 100755 index 00000000..f317720b --- /dev/null +++ b/examples/echo/udp-echo/server/assembly/windows/release.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env bash +# ****************************************************** +# DESC : build script for release env +# AUTHOR : Alex Stocks +# VERSION : 1.0 +# LICENCE : Apache License 2.0 +# EMAIL : alexstocks@foxmail.com +# MOD : 2016-07-12 16:34 +# FILE : test.sh +# ****************************************************** + + +set -e + +export GOOS=windows +export GOARCH=amd64 + +export PROFILE="release" +export PROJECT_HOME=`pwd` + +if [ -f "${PROJECT_HOME}/assembly/common/app.properties" ]; then + . ${PROJECT_HOME}/assembly/common/app.properties +fi + +if [ -f "${PROJECT_HOME}/assembly/common/build.sh" ]; then + sh ${PROJECT_HOME}/assembly/common/build.sh +fi diff --git a/examples/echo/udp-echo/server/assembly/windows/test.sh b/examples/echo/udp-echo/server/assembly/windows/test.sh new file mode 100644 index 00000000..0edc2bff --- /dev/null +++ b/examples/echo/udp-echo/server/assembly/windows/test.sh @@ -0,0 +1,28 @@ +#!/usr/bin/env bash +# ****************************************************** +# DESC : build script for test env +# AUTHOR : Alex Stocks +# VERSION : 1.0 +# LICENCE : Apache License 2.0 +# EMAIL : alexstocks@foxmail.com +# MOD : 2016-07-12 16:34 +# FILE : test.sh +# ****************************************************** + + +set -e + +export GOOS=windows +export GOARCH=amd64 + +export PROFILE="test" +export PROJECT_HOME=`pwd` + +if [ -f "${PROJECT_HOME}/assembly/common/app.properties" ]; then + . ${PROJECT_HOME}/assembly/common/app.properties +fi + + +if [ -f "${PROJECT_HOME}/assembly/common/build.sh" ]; then + sh ${PROJECT_HOME}/assembly/common/build.sh +fi diff --git a/examples/echo/udp-echo/server/profiles/dev/config.toml b/examples/echo/udp-echo/server/profiles/dev/config.toml new file mode 100644 index 00000000..a88f6e64 --- /dev/null +++ b/examples/echo/udp-echo/server/profiles/dev/config.toml @@ -0,0 +1,31 @@ +# toml configure file +# toml中key的首字母可以小写,但是对应的golang中的struct成员首字母必须大写 + +AppName = "ECHO-SERVER" + +Host = "127.0.0.1" +# Host = "192.168.35.1" +# Host = "192.168.8.3" +# Ports = ["10000", "20000"] +Ports = "10000" +ProfilePort = 10086 + +# session +# client与server之间连接的超时时间 +SessionTimeout = "20s" +SessionNumber = 700 + +# app +FailFastTimeout = "3s" + +# tcp +[GettySessionParam] + CompressEncoding = true + UdpRBufSize = 262144 + UdpWBufSize = 524288 + PkgWQSize = 512 + UdpReadTimeout = "10s" + UdpWriteTimeout = "5s" + WaitTimeout = "1s" + MaxMsgLen = 128 + SessionName = "echo-server" diff --git a/examples/echo/udp-echo/server/profiles/dev/log.xml b/examples/echo/udp-echo/server/profiles/dev/log.xml new file mode 100644 index 00000000..8b84716c --- /dev/null +++ b/examples/echo/udp-echo/server/profiles/dev/log.xml @@ -0,0 +1,63 @@ + + + stdout + console + + DEBUG + + + debug_file + file + DEBUG + logs/debug.log + [%D %T] [%L] [%S] %M + true + 0M + 0K + true + + + info_file + file + INFO + logs/info.log + + [%D %T] [%L] [%S] %M + true + 0M + 0K + true + + + warn_file + file + WARNING + logs/warn.log + [%D %T] [%L] [%S] %M + true + 0M + 0K + true + + + error_file + file + ERROR + logs/error.log + [%D %T] [%L] [%S] %M + true + 0M + 0K + true + + diff --git a/examples/echo/udp-echo/server/profiles/release/config.toml b/examples/echo/udp-echo/server/profiles/release/config.toml new file mode 100644 index 00000000..a88f6e64 --- /dev/null +++ b/examples/echo/udp-echo/server/profiles/release/config.toml @@ -0,0 +1,31 @@ +# toml configure file +# toml中key的首字母可以小写,但是对应的golang中的struct成员首字母必须大写 + +AppName = "ECHO-SERVER" + +Host = "127.0.0.1" +# Host = "192.168.35.1" +# Host = "192.168.8.3" +# Ports = ["10000", "20000"] +Ports = "10000" +ProfilePort = 10086 + +# session +# client与server之间连接的超时时间 +SessionTimeout = "20s" +SessionNumber = 700 + +# app +FailFastTimeout = "3s" + +# tcp +[GettySessionParam] + CompressEncoding = true + UdpRBufSize = 262144 + UdpWBufSize = 524288 + PkgWQSize = 512 + UdpReadTimeout = "10s" + UdpWriteTimeout = "5s" + WaitTimeout = "1s" + MaxMsgLen = 128 + SessionName = "echo-server" diff --git a/examples/echo/udp-echo/server/profiles/release/log.xml b/examples/echo/udp-echo/server/profiles/release/log.xml new file mode 100644 index 00000000..8b84716c --- /dev/null +++ b/examples/echo/udp-echo/server/profiles/release/log.xml @@ -0,0 +1,63 @@ + + + stdout + console + + DEBUG + + + debug_file + file + DEBUG + logs/debug.log + [%D %T] [%L] [%S] %M + true + 0M + 0K + true + + + info_file + file + INFO + logs/info.log + + [%D %T] [%L] [%S] %M + true + 0M + 0K + true + + + warn_file + file + WARNING + logs/warn.log + [%D %T] [%L] [%S] %M + true + 0M + 0K + true + + + error_file + file + ERROR + logs/error.log + [%D %T] [%L] [%S] %M + true + 0M + 0K + true + + diff --git a/examples/echo/udp-echo/server/profiles/test/config.toml b/examples/echo/udp-echo/server/profiles/test/config.toml new file mode 100644 index 00000000..a88f6e64 --- /dev/null +++ b/examples/echo/udp-echo/server/profiles/test/config.toml @@ -0,0 +1,31 @@ +# toml configure file +# toml中key的首字母可以小写,但是对应的golang中的struct成员首字母必须大写 + +AppName = "ECHO-SERVER" + +Host = "127.0.0.1" +# Host = "192.168.35.1" +# Host = "192.168.8.3" +# Ports = ["10000", "20000"] +Ports = "10000" +ProfilePort = 10086 + +# session +# client与server之间连接的超时时间 +SessionTimeout = "20s" +SessionNumber = 700 + +# app +FailFastTimeout = "3s" + +# tcp +[GettySessionParam] + CompressEncoding = true + UdpRBufSize = 262144 + UdpWBufSize = 524288 + PkgWQSize = 512 + UdpReadTimeout = "10s" + UdpWriteTimeout = "5s" + WaitTimeout = "1s" + MaxMsgLen = 128 + SessionName = "echo-server" diff --git a/examples/echo/udp-echo/server/profiles/test/log.xml b/examples/echo/udp-echo/server/profiles/test/log.xml new file mode 100644 index 00000000..8b84716c --- /dev/null +++ b/examples/echo/udp-echo/server/profiles/test/log.xml @@ -0,0 +1,63 @@ + + + stdout + console + + DEBUG + + + debug_file + file + DEBUG + logs/debug.log + [%D %T] [%L] [%S] %M + true + 0M + 0K + true + + + info_file + file + INFO + logs/info.log + + [%D %T] [%L] [%S] %M + true + 0M + 0K + true + + + warn_file + file + WARNING + logs/warn.log + [%D %T] [%L] [%S] %M + true + 0M + 0K + true + + + error_file + file + ERROR + logs/error.log + [%D %T] [%L] [%S] %M + true + 0M + 0K + true + + diff --git a/examples/echo/ws-echo/change_log.md b/examples/echo/ws-echo/change_log.md new file mode 100644 index 00000000..4afd3dac --- /dev/null +++ b/examples/echo/ws-echo/change_log.md @@ -0,0 +1,40 @@ +# ws-echo # +--- +*getty websocket code examples of Echo Example* + +## LICENSE ## +--- + +> LICENCE : Apache License 2.0 + +## develop history ## +--- + +- 2019/09/05 + > feature + * add writev + +- 2018/06/24 + > improvement + * delete this, using name abbreviation instead. + +- 2018/03/17 + > improvement + * use getty 0.8.2 + +- 2018/03/09 + > improvement + * use getty 0.8.1 + +- 2017/04/27 + > improvement + * enable wss client just using cert file; + * js-client can connect wss server by allow-insecure-localhost (chrome://flags/#allow-insecure-localhost) + +- 2016/11/19 + > 1 add client/app/config.go:GettySessionParam{CompressEncoding} to test compress websocket compression extension. + > + > 2 add server/app/config.go:GettySessionParam{CompressEncoding} to test compress websocket compression extension. + > + > 3 Pls attention that ie does not support weboscket compression extension while the latest chrome support it while echo/ws-echo/server enable websocket compress function. I have not test firefox and edge. + > As of version 19, Chrome will apparently compress WebSocket traffic automatically when the server supports it. diff --git a/examples/echo/ws-echo/client/app/client.go b/examples/echo/ws-echo/client/app/client.go new file mode 100644 index 00000000..3a469ec0 --- /dev/null +++ b/examples/echo/ws-echo/client/app/client.go @@ -0,0 +1,153 @@ +/* + * 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. + */ + +package main + +import ( + "math/rand" + "sync" + "time" +) + +import ( + log "github.com/AlexStocks/log4go" + getty "github.com/apache/dubbo-getty" +) + +var ( + reqID uint32 + src = rand.NewSource(time.Now().UnixNano()) +) + +func init() { + rand.Seed(time.Now().UnixNano()) +} + +//////////////////////////////////////////////////////////////////// +// echo client +//////////////////////////////////////////////////////////////////// + +type EchoClient struct { + lock sync.RWMutex + sessions []*clientEchoSession + gettyClient getty.Client +} + +func (c *EchoClient) isAvailable() bool { + if c.selectSession() == nil { + return false + } + + return true +} + +func (c *EchoClient) close() { + c.lock.Lock() + defer c.lock.Unlock() + if c.gettyClient != nil { + c.gettyClient.Close() + c.gettyClient = nil + for _, s := range c.sessions { + log.Info("close client session{%s, last active:%s, request number:%d}", + s.session.Stat(), s.session.GetActive().String(), s.reqNum) + s.session.Close() + } + c.sessions = c.sessions[:0] + } +} + +func (c *EchoClient) selectSession() getty.Session { + // get route server session + c.lock.RLock() + defer c.lock.RUnlock() + count := len(c.sessions) + if count == 0 { + log.Info("client session array is nil...") + return nil + } + + return c.sessions[rand.Int31n(int32(count))].session +} + +func (c *EchoClient) addSession(session getty.Session) { + log.Debug("add session{%s}", session.Stat()) + if session == nil { + return + } + + c.lock.Lock() + c.sessions = append(c.sessions, &clientEchoSession{session: session}) + c.lock.Unlock() +} + +func (c *EchoClient) removeSession(session getty.Session) { + if session == nil { + return + } + + c.lock.Lock() + + for i, s := range c.sessions { + if s.session == session { + c.sessions = append(c.sessions[:i], c.sessions[i+1:]...) + log.Debug("delete session{%s}, its index{%d}", session.Stat(), i) + break + } + } + log.Info("after remove session{%s}, left session number:%d", session.Stat(), len(c.sessions)) + + c.lock.Unlock() +} + +func (c *EchoClient) updateSession(session getty.Session) { + if session == nil { + return + } + + c.lock.Lock() + + for i, s := range c.sessions { + if s.session == session { + c.sessions[i].reqNum++ + break + } + } + + c.lock.Unlock() +} + +func (c *EchoClient) getClientEchoSession(session getty.Session) (clientEchoSession, error) { + var ( + err error + echoSession clientEchoSession + ) + + c.lock.Lock() + + err = errSessionNotExist + for _, s := range c.sessions { + if s.session == session { + echoSession = *s + err = nil + break + } + } + + c.lock.Unlock() + + return echoSession, err +} diff --git a/examples/echo/ws-echo/client/app/config.go b/examples/echo/ws-echo/client/app/config.go new file mode 100644 index 00000000..905c7c44 --- /dev/null +++ b/examples/echo/ws-echo/client/app/config.go @@ -0,0 +1,158 @@ +/* + * 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. + */ + +package main + +import ( + "fmt" + "os" + "path" + "time" +) + +import ( + // "github.com/AlexStocks/goext/log" + log "github.com/AlexStocks/log4go" + config "github.com/koding/multiconfig" +) + +const ( + APP_CONF_FILE = "APP_CONF_FILE" + APP_LOG_CONF_FILE = "APP_LOG_CONF_FILE" +) + +var conf *Config + +type ( + GettySessionParam struct { + CompressEncoding bool `default:"false"` + TcpNoDelay bool `default:"true"` + TcpKeepAlive bool `default:"true"` + TcpRBufSize int `default:"262144"` + TcpWBufSize int `default:"65536"` + PkgWQSize int `default:"1024"` + TcpReadTimeout string `default:"1s"` + tcpReadTimeout time.Duration + TcpWriteTimeout string `default:"5s"` + tcpWriteTimeout time.Duration + WaitTimeout string `default:"7s"` + waitTimeout time.Duration + MaxMsgLen int `default:"1024"` + SessionName string `default:"echo-client"` + } + + // Config holds supported types by the multiconfig package + Config struct { + // local + AppName string `default:"echo-client"` + LocalHost string `default:"127.0.0.1"` + + // server + ServerHost string `default:"127.0.0.1"` + ServerPort int `default:"10000"` + ServerPath string `default:"/echo"` + ProfilePort int `default:"10086"` + + // session pool + ConnectionNum int `default:"16"` + + // heartbeat + HeartbeatPeriod string `default:"15s"` + heartbeatPeriod time.Duration + + // session + SessionTimeout string `default:"60s"` + sessionTimeout time.Duration + + // echo + EchoString string `default:"hello"` + EchoTimes int `default:"10"` + + // app + FailFastTimeout string `default:"5s"` + failFastTimeout time.Duration + + // session tcp parameters + GettySessionParam GettySessionParam `required:"true"` + } +) + +func initConf() { + var ( + err error + confFile string + ) + + // configure + confFile = os.Getenv(APP_CONF_FILE) + if confFile == "" { + panic(fmt.Sprintf("application configure file name is nil")) + return // I know it is of no usage. Just Err Protection. + } + if path.Ext(confFile) != ".toml" { + panic(fmt.Sprintf("application configure file name{%v} suffix must be .toml", confFile)) + return + } + conf = new(Config) + config.MustLoadWithPath(confFile, conf) + conf.heartbeatPeriod, err = time.ParseDuration(conf.HeartbeatPeriod) + if err != nil { + panic(fmt.Sprintf("time.ParseDuration(HeartbeatPeroid{%#v}) = error{%v}", conf.HeartbeatPeriod, err)) + return + } + conf.sessionTimeout, err = time.ParseDuration(conf.SessionTimeout) + if err != nil { + panic(fmt.Sprintf("time.ParseDuration(SessionTimeout{%#v}) = error{%v}", conf.SessionTimeout, err)) + return + } + conf.failFastTimeout, err = time.ParseDuration(conf.FailFastTimeout) + if err != nil { + panic(fmt.Sprintf("time.ParseDuration(FailFastTimeout{%#v}) = error{%v}", conf.FailFastTimeout, err)) + return + } + conf.GettySessionParam.tcpReadTimeout, err = time.ParseDuration(conf.GettySessionParam.TcpReadTimeout) + if err != nil { + panic(fmt.Sprintf("time.ParseDuration(TcpReadTimeout{%#v}) = error{%v}", conf.GettySessionParam.TcpReadTimeout, err)) + return + } + conf.GettySessionParam.tcpWriteTimeout, err = time.ParseDuration(conf.GettySessionParam.TcpWriteTimeout) + if err != nil { + panic(fmt.Sprintf("time.ParseDuration(TcpWriteTimeout{%#v}) = error{%v}", conf.GettySessionParam.TcpWriteTimeout, err)) + return + } + conf.GettySessionParam.waitTimeout, err = time.ParseDuration(conf.GettySessionParam.WaitTimeout) + if err != nil { + panic(fmt.Sprintf("time.ParseDuration(WaitTimeout{%#v}) = error{%v}", conf.GettySessionParam.WaitTimeout, err)) + return + } + // gxlog.CInfo("config{%#v}\n", conf) + + // log + confFile = os.Getenv(APP_LOG_CONF_FILE) + if confFile == "" { + panic(fmt.Sprintf("log configure file name is nil")) + return + } + if path.Ext(confFile) != ".xml" { + panic(fmt.Sprintf("log configure file name{%v} suffix must be .xml", confFile)) + return + } + log.LoadConfiguration(confFile) + log.Info("config{%#v}", conf) + + return +} diff --git a/examples/echo/ws-echo/client/app/echo.go b/examples/echo/ws-echo/client/app/echo.go new file mode 100644 index 00000000..bdc3e5d3 --- /dev/null +++ b/examples/echo/ws-echo/client/app/echo.go @@ -0,0 +1,146 @@ +/* + * 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. + */ + +package main + +import ( + "bytes" + "encoding/binary" + "errors" + "fmt" + "unsafe" +) + +import ( + log "github.com/AlexStocks/log4go" +) + +//////////////////////////////////////////// +// echo command +//////////////////////////////////////////// + +type echoCommand uint32 + +const ( + echoCmd = iota +) + +var echoCommandStrings = [...]string{ + "echo", +} + +func (c echoCommand) String() string { + return echoCommandStrings[c] +} + +//////////////////////////////////////////// +// EchoPkgHandler +//////////////////////////////////////////// + +const ( + echoPkgMagic = 0x20160905 + maxEchoStringLen = 0xff +) + +var ( + ErrNotEnoughStream = errors.New("packet stream is not enough") + ErrTooLargePackage = errors.New("package length is exceed the echo package's legal maximum length.") + ErrIllegalMagic = errors.New("package magic is not right.") +) + +var echoPkgHeaderLen int + +func init() { + echoPkgHeaderLen = (int)((uint)(unsafe.Sizeof(EchoPkgHeader{}))) +} + +type EchoPkgHeader struct { + Magic uint32 + LogID uint32 // log id + + Sequence uint32 // request/response sequence + ServiceID uint32 // service id + + Command uint32 // operation command code + Code int32 // error code + + Len uint16 // body length + _ uint16 + _ int32 // reserved, maybe used as package md5 checksum +} + +type EchoPackage struct { + H EchoPkgHeader + B string +} + +func (p EchoPackage) String() string { + return fmt.Sprintf("log id:%d, sequence:%d, command:%s, echo string:%s", + p.H.LogID, p.H.Sequence, (echoCommand(p.H.Command)).String(), p.B) +} + +func (p EchoPackage) Marshal() (*bytes.Buffer, error) { + var ( + err error + buf *bytes.Buffer + ) + + buf = &bytes.Buffer{} + err = binary.Write(buf, binary.LittleEndian, p.H) + if err != nil { + return nil, err + } + buf.WriteByte((byte)(len(p.B))) + buf.WriteString(p.B) + + return buf, nil +} + +func (p *EchoPackage) Unmarshal(buf *bytes.Buffer) (int, error) { + var ( + err error + len byte + ) + + if buf.Len() < echoPkgHeaderLen { + return 0, ErrNotEnoughStream + } + + // header + err = binary.Read(buf, binary.LittleEndian, &(p.H)) + if err != nil { + return 0, err + } + if p.H.Magic != echoPkgMagic { + log.Error("@p.H.Magic{%x}, right magic{%x}", p.H.Magic, echoPkgMagic) + return 0, ErrIllegalMagic + } + if buf.Len() < (int)(p.H.Len) { + return 0, ErrNotEnoughStream + } + if maxEchoStringLen < p.H.Len-1 { + return 0, ErrTooLargePackage + } + + len, err = buf.ReadByte() + if err != nil { + return 0, nil + } + p.B = (string)(buf.Next((int)(len))) + + return (int)(p.H.Len) + echoPkgHeaderLen, nil +} diff --git a/examples/echo/ws-echo/client/app/handler.go b/examples/echo/ws-echo/client/app/handler.go new file mode 100644 index 00000000..7aa4328a --- /dev/null +++ b/examples/echo/ws-echo/client/app/handler.go @@ -0,0 +1,89 @@ +/* + * 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. + */ + +package main + +import ( + "errors" + "time" +) + +import ( + log "github.com/AlexStocks/log4go" + getty "github.com/apache/dubbo-getty" +) + +var ( + errSessionNotExist = errors.New("session not exist!") + echoMsgHandler = newEchoMessageHandler() +) + +//////////////////////////////////////////// +// EchoMessageHandler +//////////////////////////////////////////// + +type clientEchoSession struct { + session getty.Session + reqNum int32 +} + +type EchoMessageHandler struct{} + +func newEchoMessageHandler() *EchoMessageHandler { + return &EchoMessageHandler{} +} + +func (h *EchoMessageHandler) OnOpen(session getty.Session) error { + client.addSession(session) + + return nil +} + +func (h *EchoMessageHandler) OnError(session getty.Session, err error) { + log.Info("session{%s} got error{%v}, will be closed.", session.Stat(), err) + client.removeSession(session) +} + +func (h *EchoMessageHandler) OnClose(session getty.Session) { + log.Info("session{%s} is closing......", session.Stat()) + client.removeSession(session) +} + +func (h *EchoMessageHandler) OnMessage(session getty.Session, pkg interface{}) { + p, ok := pkg.(*EchoPackage) + if !ok { + log.Error("illegal packge{%#v}", pkg) + return + } + + log.Debug("get echo package{%s}", p) + client.updateSession(session) +} + +func (h *EchoMessageHandler) OnCron(session getty.Session) { + clientEchoSession, err := client.getClientEchoSession(session) + if err != nil { + log.Error("client.getClientSession(session{%s}) = error{%#v}", session.Stat(), err) + return + } + if conf.sessionTimeout.Nanoseconds() < time.Since(session.GetActive()).Nanoseconds() { + log.Warn("session{%s} timeout{%s}, reqNum{%d}", + session.Stat(), time.Since(session.GetActive()).String(), clientEchoSession.reqNum) + client.removeSession(session) + return + } +} diff --git a/examples/echo/ws-echo/client/app/main.go b/examples/echo/ws-echo/client/app/main.go new file mode 100644 index 00000000..819c296d --- /dev/null +++ b/examples/echo/ws-echo/client/app/main.go @@ -0,0 +1,199 @@ +/* + * 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. + */ + +package main + +import ( + // "flag" + "fmt" + "net" + "net/http" + _ "net/http/pprof" + "os" + "os/signal" + + // "strings" + "crypto/tls" + "sync/atomic" + "syscall" + "time" +) + +import ( + gxlog "github.com/AlexStocks/goext/log" + gxnet "github.com/AlexStocks/goext/net" + gxtime "github.com/AlexStocks/goext/time" + log "github.com/AlexStocks/log4go" + getty "github.com/apache/dubbo-getty" +) + +const ( + pprofPath = "/debug/pprof/" +) + +var client EchoClient + +//////////////////////////////////////////////////////////////////// +// main +//////////////////////////////////////////////////////////////////// + +func main() { + initConf() + + initProfiling() + + initClient() + gxlog.CInfo("%s starts successfull!", conf.AppName) + log.Info("%s starts successfull!\n", conf.AppName) + + go test() + + initSignal() +} + +func initProfiling() { + var addr string + + addr = gxnet.HostAddress(conf.LocalHost, conf.ProfilePort) + log.Info("App Profiling startup on address{%v}", addr+pprofPath) + go func() { + log.Info(http.ListenAndServe(addr, nil)) + }() +} + +func newSession(session getty.Session) error { + var ( + flag1, flag2 bool + tcpConn *net.TCPConn + ) + + _, flag1 = session.Conn().(*tls.Conn) + tcpConn, flag2 = session.Conn().(*net.TCPConn) + if !flag1 && !flag2 { + panic(fmt.Sprintf("%s, session.conn{%#v} is not tcp/tls connection\n", session.Stat(), session.Conn())) + } + + if conf.GettySessionParam.CompressEncoding { + session.SetCompressType(getty.CompressZip) + } + // else { + // session.SetCompressType(getty.CompressNone) + //} + + if flag2 { + tcpConn.SetNoDelay(conf.GettySessionParam.TcpNoDelay) + tcpConn.SetKeepAlive(conf.GettySessionParam.TcpKeepAlive) + tcpConn.SetReadBuffer(conf.GettySessionParam.TcpRBufSize) + tcpConn.SetWriteBuffer(conf.GettySessionParam.TcpWBufSize) + } + + session.SetName(conf.GettySessionParam.SessionName) + session.SetMaxMsgLen(conf.GettySessionParam.MaxMsgLen) + session.SetPkgHandler(echoPkgHandler) + session.SetEventListener(echoMsgHandler) + session.SetReadTimeout(conf.GettySessionParam.tcpReadTimeout) + session.SetWriteTimeout(conf.GettySessionParam.tcpWriteTimeout) + session.SetCronPeriod((int)(conf.heartbeatPeriod.Nanoseconds() / 1e6)) + session.SetWaitTime(conf.GettySessionParam.waitTimeout) + log.Debug("client new session:%s\n", session.Stat()) + + return nil +} + +func initClient() { + client.gettyClient = getty.NewWSClient( + getty.WithServerAddress(gxnet.WSHostAddress(conf.ServerHost, conf.ServerPort, conf.ServerPath)), + getty.WithConnectionNumber((int)(conf.ConnectionNum)), + ) + + client.gettyClient.RunEventLoop(newSession) +} + +func uninitClient() { + client.close() +} + +func initSignal() { + // signal.Notify的ch信道是阻塞的(signal.Notify不会阻塞发送信号), 需要设置缓冲 + signals := make(chan os.Signal, 1) + // It is not possible to block SIGKILL or syscall.SIGSTOP + signal.Notify(signals, os.Interrupt, os.Kill, syscall.SIGHUP, syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT) + for { + sig := <-signals + log.Info("get signal %s", sig.String()) + switch sig { + case syscall.SIGHUP: + // reload() + default: + go time.AfterFunc(conf.failFastTimeout, func() { + // log.Warn("app exit now by force...") + // os.Exit(1) + log.Exit("app exit now by force...") + log.Close() + }) + + // 要么fastFailTimeout时间内执行完毕下面的逻辑然后程序退出,要么执行上面的超时函数程序强行退出 + uninitClient() + // fmt.Println("app exit now...") + log.Exit("app exit now...") + log.Close() + return + } + } +} + +func echo() { + var pkg EchoPackage + pkg.H.Magic = echoPkgMagic + pkg.H.LogID = (uint32)(src.Int63()) + pkg.H.Sequence = atomic.AddUint32(&reqID, 1) + // pkg.H.ServiceID = 0 + pkg.H.Command = echoCmd + pkg.B = conf.EchoString + pkg.H.Len = (uint16)(len(pkg.B)) + 1 + + if session := client.selectSession(); session != nil { + _, _, err := session.WritePkg(&pkg, conf.GettySessionParam.waitTimeout) + if err != nil { + log.Warn("session.WritePkg(session{%s}, pkg{%s}, timeout{%d}) = error{%v}", + session.Stat(), pkg, conf.GettySessionParam.waitTimeout, err) + session.Close() + client.removeSession(session) + } + } +} + +func test() { + for { + if client.isAvailable() { + break + } + time.Sleep(1e6) + } + + var ( + cost int64 + counter gxtime.CountWatch + ) + counter.Start() + for i := 0; i < conf.EchoTimes; i++ { + echo() + } + cost = counter.Count() + log.Info("after loop %d times, echo cost %d ms", conf.EchoTimes, cost/1e6) + gxlog.CInfo("after loop %d times, echo cost %d ms", conf.EchoTimes, cost/1e6) +} diff --git a/examples/echo/ws-echo/client/app/readwriter.go b/examples/echo/ws-echo/client/app/readwriter.go new file mode 100644 index 00000000..8782757b --- /dev/null +++ b/examples/echo/ws-echo/client/app/readwriter.go @@ -0,0 +1,84 @@ +/* + * 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. + */ + +package main + +import ( + "bytes" + "errors" + "time" +) + +import ( + log "github.com/AlexStocks/log4go" + getty "github.com/apache/dubbo-getty" +) + +var echoPkgHandler = NewEchoPackageHandler() + +type EchoPackageHandler struct{} + +func NewEchoPackageHandler() *EchoPackageHandler { + return &EchoPackageHandler{} +} + +func (h *EchoPackageHandler) Read(ss getty.Session, data []byte) (interface{}, int, error) { + var ( + err error + len int + pkg EchoPackage + buf *bytes.Buffer + ) + + buf = bytes.NewBuffer(data) + len, err = pkg.Unmarshal(buf) + if err != nil { + if err == ErrNotEnoughStream { + return nil, 0, nil + } + + return nil, 0, err + } + + return &pkg, len, nil +} + +func (h *EchoPackageHandler) Write(ss getty.Session, pkg interface{}) ([]byte, error) { + var ( + ok bool + err error + startTime time.Time + echoPkg *EchoPackage + buf *bytes.Buffer + ) + + startTime = time.Now() + if echoPkg, ok = pkg.(*EchoPackage); !ok { + log.Error("illegal pkg:%+v\n", pkg) + return nil, errors.New("invalid echo package!") + } + + buf, err = echoPkg.Marshal() + if err != nil { + log.Warn("binary.Write(echoPkg{%#v}) = err{%#v}", echoPkg, err) + return nil, err + } + + log.Debug("WriteEchoPkgTimeMs = %s", time.Since(startTime).String()) + + return buf.Bytes(), nil +} diff --git a/examples/echo/ws-echo/client/assembly/bin/load.sh b/examples/echo/ws-echo/client/assembly/bin/load.sh new file mode 100644 index 00000000..1d038cc0 --- /dev/null +++ b/examples/echo/ws-echo/client/assembly/bin/load.sh @@ -0,0 +1,186 @@ +#!/usr/bin/env bash +# ****************************************************** +# DESC : getty app devops script +# AUTHOR : Alex Stocks +# VERSION : 1.0 +# LICENCE : LGPL V3 +# EMAIL : alexstocks@foxmail.com +# MOD : 2016-05-13 02:01 +# FILE : load.sh +# ****************************************************** + +APP_NAME="APPLICATION_NAME" +APP_ARGS="" +SLEEP_INTERVAL=5 +MAX_LIFETIME=4000 + +PROJECT_HOME="" +OS_NAME=`uname` +if [[ ${OS_NAME} != "Windows" ]]; then + PROJECT_HOME=`pwd` + PROJECT_HOME=${PROJECT_HOME}"/" +else + APP_NAME="APPLICATION_NAME.exe" +fi + +export APP_CONF_FILE=${PROJECT_HOME}"TARGET_CONF_FILE" +export APP_LOG_CONF_FILE=${PROJECT_HOME}"TARGET_LOG_CONF_FILE" +# export GOTRACEBACK=system +# export GODEBUG=gctrace=1 + +usage() { + echo "Usage: $0 start" + echo " $0 stop" + echo " $0 term" + echo " $0 restart" + echo " $0 list" + echo " $0 monitor" + echo " $0 crontab" + exit +} + +start() { + APP_LOG_PATH=${PROJECT_HOME}"logs/" + mkdir -p ${APP_LOG_PATH} + APP_BIN=${PROJECT_HOME}sbin/${APP_NAME} + chmod u+x ${APP_BIN} + # CMD="nohup ${APP_BIN} ${APP_ARGS} >>${APP_NAME}.nohup.out 2>&1 &" + CMD="${APP_BIN}" + eval ${CMD} + PID=`ps aux | grep -w ${APP_NAME} | grep -v grep | awk '{print $2}'` + if [[ ${OS_NAME} != "Linux" && ${OS_NAME} != "Darwin" ]]; then + PID=`ps aux | grep -w ${APP_NAME} | grep -v grep | awk '{print $1}'` + fi + CUR=`date +%FT%T` + if [ "${PID}" != "" ]; then + for p in ${PID} + do + echo "start ${APP_NAME} ( pid =" ${p} ") at " ${CUR} + done + fi +} + +stop() { + PID=`ps aux | grep -w ${APP_NAME} | grep -v grep | awk '{print $2}'` + if [[ ${OS_NAME} != "Linux" && ${OS_NAME} != "Darwin" ]]; then + PID=`ps aux | grep -w ${APP_NAME} | grep -v grep | awk '{print $1}'` + fi + if [ "${PID}" != "" ]; + then + for ps in ${PID} + do + echo "kill -SIGINT ${APP_NAME} ( pid =" ${ps} ")" + kill -2 ${ps} + done + fi +} + + +term() { + PID=`ps aux | grep -w ${APP_NAME} | grep -v grep | awk '{print $2}'` + if [[ ${OS_NAME} != "Linux" && ${OS_NAME} != "Darwin" ]]; then + PID=`ps aux | grep -w ${APP_NAME} | grep -v grep | awk '{print $1}'` + fi + if [ "${PID}" != "" ]; + then + for ps in ${PID} + do + echo "kill -9 ${APP_NAME} ( pid =" ${ps} ")" + kill -9 ${ps} + done + fi +} + +list() { + PID=`ps aux | grep -w ${APP_NAME} | grep -v grep | awk '{printf("%s,%s,%s,%s\n", $1, $2, $9, $10)}'` + if [[ ${OS_NAME} != "Linux" && ${OS_NAME} != "Darwin" ]]; then + PID=`ps aux | grep -w ${APP_NAME} | grep -v grep | awk '{printf("%s,%s,%s,%s,%s\n", $1, $4, $6, $7, $8)}'` + fi + + if [ "${PID}" != "" ]; then + echo "list ${APP_NAME}" + + if [[ ${OS_NAME} == "Linux" || ${OS_NAME} == "Darwin" ]]; then + echo "index: user, pid, start, duration" + else + echo "index: PID, WINPID, UID, STIME, COMMAND" + fi + idx=0 + for ps in ${PID} + do + echo "${idx}: ${ps}" + ((idx ++)) + done + fi +} + +monitor() { + idx=0 + while true; do + PID=`ps aux | grep -w ${APP_NAME} | grep -v grep | awk '{print $2}'` + if [[ ${OS_NAME} != "Linux" && ${OS_NAME} != "Darwin" ]]; then + PID=`ps aux | grep -w ${APP_NAME} | grep -v grep | awk '{print $1}'` + fi + if [[ "${PID}" == "" ]]; then + start + idx=0 + fi + + ((LIFE=idx*${SLEEP_INTERVAL})) + echo "${APP_NAME} ( pid = " ${PID} ") has been working in normal state for " $LIFE " seconds." + ((idx ++)) + sleep ${SLEEP_INTERVAL} + done +} + +crontab() { + idx=0 + while true; do + PID=`ps aux | grep -w ${APP_NAME} | grep -v grep | awk '{print $2}'` + if [[ ${OS_NAME} != "Linux" && ${OS_NAME} != "Darwin" ]]; then + PID=`ps aux | grep -w ${APP_NAME} | grep -v grep | awk '{print $1}'` + fi + if [[ "${PID}" == "" ]]; then + start + idx=0 + fi + + ((LIFE=idx*${SLEEP_INTERVAL})) + echo "${APP_NAME} ( pid = " ${PID} ") has been working in normal state for " $LIFE " seconds." + ((idx ++)) + sleep ${SLEEP_INTERVAL} + if [[ ${LIFE} -gt ${MAX_LIFETIME} ]]; then + kill -9 ${PID} + fi + done +} + +opt=$1 +case C"$opt" in + Cstart) + start + ;; + Cstop) + stop + ;; + Cterm) + term + ;; + Crestart) + term + start + ;; + Clist) + list + ;; + Cmonitor) + monitor + ;; + Ccrontab) + crontab + ;; + C*) + usage + ;; +esac + diff --git a/examples/echo/ws-echo/client/assembly/common/app.properties b/examples/echo/ws-echo/client/assembly/common/app.properties new file mode 100644 index 00000000..369cc336 --- /dev/null +++ b/examples/echo/ws-echo/client/assembly/common/app.properties @@ -0,0 +1,17 @@ +# getty application configure script +# ****************************************************** +# DESC : application environment variable +# AUTHOR : Alex Stocks +# VERSION : 1.0 +# LICENCE : Apache License 2.0 +# EMAIL : alexstocks@foxmail.com +# MOD : 2016-07-12 16:29 +# FILE : app.properties +# ****************************************************** + +export TARGET_EXEC_NAME="echo_client" +export BUILD_PACKAGE="app" + +export TARGET_CONF_FILE="conf/config.toml" +export TARGET_LOG_CONF_FILE="conf/log.xml" + diff --git a/examples/echo/ws-echo/client/assembly/common/build.sh b/examples/echo/ws-echo/client/assembly/common/build.sh new file mode 100644 index 00000000..00763725 --- /dev/null +++ b/examples/echo/ws-echo/client/assembly/common/build.sh @@ -0,0 +1,77 @@ +#!/usr/bin/env bash +# ****************************************************** +# DESC : build script +# AUTHOR : Alex Stocks +# VERSION : 1.0 +# LICENCE : Apache License 2.0 +# EMAIL : alexstocks@foxmail.com +# MOD : 2016-07-12 16:28 +# FILE : build.sh +# ****************************************************** + +rm -rf target/ + +PROJECT_HOME=`pwd` +TARGET_FOLDER=${PROJECT_HOME}/target/${GOOS} + +TARGET_SBIN_NAME=${TARGET_EXEC_NAME} +version=`cat app/version.go | grep Version | awk -F '=' '{print $2}' | awk -F '"' '{print $2}'` +if [[ ${GOOS} == "windows" ]]; then + TARGET_SBIN_NAME=${TARGET_SBIN_NAME}.exe +fi +TARGET_NAME=${TARGET_FOLDER}/${TARGET_SBIN_NAME} +if [[ $PROFILE == "dev" || $PROFILE == "test" ]]; then + # GFLAGS=-gcflags "-N -l" -race -x -v # -x会把go build的详细过程输出 + # GFLAGS=-gcflags "-N -l" -race -v + # GFLAGS="-gcflags \"-N -l\" -v" + cd ${BUILD_PACKAGE} && GOOS=$GOOS GOARCH=$GOARCH go build -gcflags "-N -l" -x -v -i -o ${TARGET_NAME} && cd - +else + # -s去掉符号表(然后panic时候的stack trace就没有任何文件名/行号信息了,这个等价于普通C/C++程序被strip的效果), + # -w去掉DWARF调试信息,得到的程序就不能用gdb调试了。-s和-w也可以分开使用,一般来说如果不打算用gdb调试, + # -w基本没啥损失。-s的损失就有点大了。 + cd ${BUILD_PACKAGE} && GOOS=$GOOS GOARCH=$GOARCH go build -ldflags "-w" -x -v -i -o ${TARGET_NAME} && cd - +fi + +TAR_NAME=${TARGET_EXEC_NAME}-${version}-`date "+%Y%m%d-%H%M"`-${PROFILE} + +mkdir -p ${TARGET_FOLDER}/${TAR_NAME} + +SBIN_DIR=${TARGET_FOLDER}/${TAR_NAME}/sbin +BIN_DIR=${TARGET_FOLDER}/${TAR_NAME} +CONF_DIR=${TARGET_FOLDER}/${TAR_NAME}/conf + +mkdir -p ${SBIN_DIR} +mkdir -p ${CONF_DIR} + +mv ${TARGET_NAME} ${SBIN_DIR} +cp -r assembly/bin ${BIN_DIR} +cd ${BIN_DIR}/bin/ && mv load.sh load_${TARGET_EXEC_NAME}.sh && cd - + +platform=$(uname) +# modify APPLICATION_NAME +if [ ${platform} == "Darwin" ]; then + sed -i "" "s~APPLICATION_NAME~${TARGET_EXEC_NAME}~g" ${BIN_DIR}/bin/* +else + sed -i "s~APPLICATION_NAME~${TARGET_EXEC_NAME}~g" ${BIN_DIR}/bin/* +fi + +# modify TARGET_CONF_FILE +if [ ${platform} == "Darwin" ]; then + sed -i "" "s~TARGET_CONF_FILE~${TARGET_CONF_FILE}~g" ${BIN_DIR}/bin/* +else + sed -i "s~TARGET_CONF_FILE~${TARGET_CONF_FILE}~g" ${BIN_DIR}/bin/* +fi + +# modify TARGET_LOG_CONF_FILE +if [ ${platform} == "Darwin" ]; then + sed -i "" "s~TARGET_LOG_CONF_FILE~${TARGET_LOG_CONF_FILE}~g" ${BIN_DIR}/bin/* +else + sed -i "s~TARGET_LOG_CONF_FILE~${TARGET_LOG_CONF_FILE}~g" ${BIN_DIR}/bin/* +fi + +cp -r profiles/${PROFILE}/* ${CONF_DIR} + +cd ${TARGET_FOLDER} + +tar czf ${TAR_NAME}.tar.gz ${TAR_NAME}/* + diff --git a/examples/echo/ws-echo/client/assembly/linux/dev.sh b/examples/echo/ws-echo/client/assembly/linux/dev.sh new file mode 100755 index 00000000..62c82fe6 --- /dev/null +++ b/examples/echo/ws-echo/client/assembly/linux/dev.sh @@ -0,0 +1,28 @@ +#!/usr/bin/env bash +# ****************************************************** +# DESC : build script for dev env +# AUTHOR : Alex Stocks +# VERSION : 1.0 +# LICENCE : Apache License 2.0 +# EMAIL : alexstocks@foxmail.com +# MOD : 2016-07-12 16:34 +# FILE : dev.sh +# ****************************************************** + + +set -e + +export GOOS=linux +export GOARCH=amd64 + +export PROFILE="dev" +export PROJECT_HOME=`pwd` + +if [ -f "${PROJECT_HOME}/assembly/common/app.properties" ]; then + . ${PROJECT_HOME}/assembly/common/app.properties +fi + + +if [ -f "${PROJECT_HOME}/assembly/common/build.sh" ]; then + sh ${PROJECT_HOME}/assembly/common/build.sh +fi diff --git a/examples/echo/ws-echo/client/assembly/linux/release.sh b/examples/echo/ws-echo/client/assembly/linux/release.sh new file mode 100755 index 00000000..2aeaf6d9 --- /dev/null +++ b/examples/echo/ws-echo/client/assembly/linux/release.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env bash +# ****************************************************** +# DESC : build script for release env +# AUTHOR : Alex Stocks +# VERSION : 1.0 +# LICENCE : Apache License 2.0 +# EMAIL : alexstocks@foxmail.com +# MOD : 2016-07-12 16:34 +# FILE : test.sh +# ****************************************************** + + +set -e + +export GOOS=linux +export GOARCH=amd64 + +export PROFILE="release" +export PROJECT_HOME=`pwd` + +if [ -f "${PROJECT_HOME}/assembly/common/app.properties" ]; then + . ${PROJECT_HOME}/assembly/common/app.properties +fi + +if [ -f "${PROJECT_HOME}/assembly/common/build.sh" ]; then + sh ${PROJECT_HOME}/assembly/common/build.sh +fi diff --git a/examples/echo/ws-echo/client/assembly/linux/test.sh b/examples/echo/ws-echo/client/assembly/linux/test.sh new file mode 100755 index 00000000..1bbbefd1 --- /dev/null +++ b/examples/echo/ws-echo/client/assembly/linux/test.sh @@ -0,0 +1,28 @@ +#!/usr/bin/env bash +# ****************************************************** +# DESC : build script for test env +# AUTHOR : Alex Stocks +# VERSION : 1.0 +# LICENCE : Apache License 2.0 +# EMAIL : alexstocks@foxmail.com +# MOD : 2016-07-12 16:34 +# FILE : test.sh +# ****************************************************** + + +set -e + +export GOOS=linux +export GOARCH=amd64 + +export PROFILE="test" +export PROJECT_HOME=`pwd` + +if [ -f "${PROJECT_HOME}/assembly/common/app.properties" ]; then + . ${PROJECT_HOME}/assembly/common/app.properties +fi + + +if [ -f "${PROJECT_HOME}/assembly/common/build.sh" ]; then + sh ${PROJECT_HOME}/assembly/common/build.sh +fi diff --git a/examples/echo/ws-echo/client/assembly/mac/dev.sh b/examples/echo/ws-echo/client/assembly/mac/dev.sh new file mode 100644 index 00000000..65e46867 --- /dev/null +++ b/examples/echo/ws-echo/client/assembly/mac/dev.sh @@ -0,0 +1,28 @@ +#!/usr/bin/env bash +# ****************************************************** +# DESC : build script for dev env +# AUTHOR : Alex Stocks +# VERSION : 1.0 +# LICENCE : Apache License 2.0 +# EMAIL : alexstocks@foxmail.com +# MOD : 2016-07-12 16:34 +# FILE : dev.sh +# ****************************************************** + + +set -e + +export GOOS=darwin +export GOARCH=amd64 + +export PROFILE="dev" +export PROJECT_HOME=`pwd` + +if [ -f "${PROJECT_HOME}/assembly/common/app.properties" ]; then + . ${PROJECT_HOME}/assembly/common/app.properties +fi + + +if [ -f "${PROJECT_HOME}/assembly/common/build.sh" ]; then + sh ${PROJECT_HOME}/assembly/common/build.sh +fi diff --git a/examples/echo/ws-echo/client/assembly/mac/release.sh b/examples/echo/ws-echo/client/assembly/mac/release.sh new file mode 100755 index 00000000..688288b3 --- /dev/null +++ b/examples/echo/ws-echo/client/assembly/mac/release.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env bash +# ****************************************************** +# DESC : build script for release env +# AUTHOR : Alex Stocks +# VERSION : 1.0 +# LICENCE : Apache License 2.0 +# EMAIL : alexstocks@foxmail.com +# MOD : 2016-07-12 16:34 +# FILE : test.sh +# ****************************************************** + + +set -e + +export GOOS=darwin +export GOARCH=amd64 + +export PROFILE="release" +export PROJECT_HOME=`pwd` + +if [ -f "${PROJECT_HOME}/assembly/common/app.properties" ]; then + . ${PROJECT_HOME}/assembly/common/app.properties +fi + +if [ -f "${PROJECT_HOME}/assembly/common/build.sh" ]; then + sh ${PROJECT_HOME}/assembly/common/build.sh +fi diff --git a/examples/echo/ws-echo/client/assembly/mac/test.sh b/examples/echo/ws-echo/client/assembly/mac/test.sh new file mode 100644 index 00000000..56d6c11e --- /dev/null +++ b/examples/echo/ws-echo/client/assembly/mac/test.sh @@ -0,0 +1,28 @@ +#!/usr/bin/env bash +# ****************************************************** +# DESC : build script for test env +# AUTHOR : Alex Stocks +# VERSION : 1.0 +# LICENCE : Apache License 2.0 +# EMAIL : alexstocks@foxmail.com +# MOD : 2016-07-12 16:34 +# FILE : test.sh +# ****************************************************** + + +set -e + +export GOOS=darwin +export GOARCH=amd64 + +export PROFILE="test" +export PROJECT_HOME=`pwd` + +if [ -f "${PROJECT_HOME}/assembly/common/app.properties" ]; then + . ${PROJECT_HOME}/assembly/common/app.properties +fi + + +if [ -f "${PROJECT_HOME}/assembly/common/build.sh" ]; then + sh ${PROJECT_HOME}/assembly/common/build.sh +fi diff --git a/examples/echo/ws-echo/client/assembly/windows/dev.sh b/examples/echo/ws-echo/client/assembly/windows/dev.sh new file mode 100644 index 00000000..94ed49ea --- /dev/null +++ b/examples/echo/ws-echo/client/assembly/windows/dev.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env bash +# ****************************************************** +# DESC : build script for dev env +# AUTHOR : Alex Stocks +# VERSION : 1.0 +# LICENCE : Apache License 2.0 +# EMAIL : alexstocks@foxmail.com +# MOD : 2016-07-12 16:34 +# FILE : dev.sh +# ****************************************************** + + +set -e + +export GOOS=windows +export GOARCH=amd64 + +export PROFILE="dev" +export PROJECT_HOME=`pwd` + +if [ -f "${PROJECT_HOME}/assembly/common/app.properties" ]; then + . ${PROJECT_HOME}/assembly/common/app.properties +fi + +if [ -f "${PROJECT_HOME}/assembly/common/build.sh" ]; then + sh ${PROJECT_HOME}/assembly/common/build.sh +fi diff --git a/examples/echo/ws-echo/client/assembly/windows/release.sh b/examples/echo/ws-echo/client/assembly/windows/release.sh new file mode 100755 index 00000000..f317720b --- /dev/null +++ b/examples/echo/ws-echo/client/assembly/windows/release.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env bash +# ****************************************************** +# DESC : build script for release env +# AUTHOR : Alex Stocks +# VERSION : 1.0 +# LICENCE : Apache License 2.0 +# EMAIL : alexstocks@foxmail.com +# MOD : 2016-07-12 16:34 +# FILE : test.sh +# ****************************************************** + + +set -e + +export GOOS=windows +export GOARCH=amd64 + +export PROFILE="release" +export PROJECT_HOME=`pwd` + +if [ -f "${PROJECT_HOME}/assembly/common/app.properties" ]; then + . ${PROJECT_HOME}/assembly/common/app.properties +fi + +if [ -f "${PROJECT_HOME}/assembly/common/build.sh" ]; then + sh ${PROJECT_HOME}/assembly/common/build.sh +fi diff --git a/examples/echo/ws-echo/client/assembly/windows/test.sh b/examples/echo/ws-echo/client/assembly/windows/test.sh new file mode 100644 index 00000000..7dd2bec5 --- /dev/null +++ b/examples/echo/ws-echo/client/assembly/windows/test.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env bash +# ****************************************************** +# DESC : build script for test env +# AUTHOR : Alex Stocks +# VERSION : 1.0 +# LICENCE : Apache License 2.0 +# EMAIL : alexstocks@foxmail.com +# MOD : 2016-07-12 16:34 +# FILE : test.sh +# ****************************************************** + + +set -e + +export GOOS=windows +export GOARCH=amd64 + +export PROFILE="test" +export PROJECT_HOME=`pwd` + +if [ -f "${PROJECT_HOME}/assembly/common/app.properties" ]; then + . ${PROJECT_HOME}/assembly/common/app.properties +fi + +if [ -f "${PROJECT_HOME}/assembly/common/build.sh" ]; then + sh ${PROJECT_HOME}/assembly/common/build.sh +fi diff --git a/examples/echo/ws-echo/client/assembly/windows/win32.sh b/examples/echo/ws-echo/client/assembly/windows/win32.sh new file mode 100644 index 00000000..f42ed8e9 --- /dev/null +++ b/examples/echo/ws-echo/client/assembly/windows/win32.sh @@ -0,0 +1,29 @@ +#!/usr/bin/env bash +# ****************************************************** +# DESC : build script for test env +# AUTHOR : Alex Stocks +# VERSION : 1.0 +# LICENCE : Apache License 2.0 +# EMAIL : alexstocks@foxmail.com +# MOD : 2016-07-12 16:34 +# FILE : test.sh +# ****************************************************** + + +set -e + +export GOOS=windows +export GOARCH=386 + +PROFILE=test + +PROJECT_HOME=`pwd` + +if [ -f "${PROJECT_HOME}/assembly/common/app.properties" ]; then +. ${PROJECT_HOME}/assembly/common/app.properties +fi + + +if [ -f "${PROJECT_HOME}/assembly/common/build.sh" ]; then +. ${PROJECT_HOME}/assembly/common/build.sh +fi diff --git a/examples/echo/ws-echo/client/profiles/dev/cert/client.crt b/examples/echo/ws-echo/client/profiles/dev/cert/client.crt new file mode 100644 index 00000000..cf747f3f --- /dev/null +++ b/examples/echo/ws-echo/client/profiles/dev/cert/client.crt @@ -0,0 +1,14 @@ +-----BEGIN CERTIFICATE----- +MIICHjCCAYegAwIBAgIQKpKqamBqmZ0hfp8sYb4uNDANBgkqhkiG9w0BAQsFADAS +MRAwDgYDVQQKEwdBY21lIENvMCAXDTcwMDEwMTAwMDAwMFoYDzIwODQwMTI5MTYw +MDAwWjASMRAwDgYDVQQKEwdBY21lIENvMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCB +iQKBgQC5Nxsk6WjeaYazRYiGxHZ5G3FXSlSjV7lZeebItdEPzO8kVPIGCSTy/M5X +Nnpp3uVDFXQub0/O5t9Y6wcuqpUGMOV+XL7MZqSZlodXm0XhNYzCAjZ+URNjTHGP +NXIqdDEG5Ba8SXMOfY6H97+QxugZoAMFZ+N83ggr12IYNO/FbQIDAQABo3MwcTAO +BgNVHQ8BAf8EBAMCAqQwEwYDVR0lBAwwCgYIKwYBBQUHAwEwDwYDVR0TAQH/BAUw +AwEB/zA5BgNVHREEMjAwgglsb2NhbGhvc3SCC2V4YW1wbGUuY29thwR/AAABhxAA +AAAAAAAAAAAAAAAAAAABMA0GCSqGSIb3DQEBCwUAA4GBAE5dr9q7ORmKZ7yZqeSL +305armc13A7UxffUajeJFujpl2jOqnb5PuKJ7fn5HQKGB0qSq3IHsFua2WONXcTW +Vn4gS0k50IaDpW+yl+ArIo0QwbjPIAcFysX10p9dVO7A1uEpHbRDzefem6r9uVGk +i7dOLEoC8hkfk6nJsNEIEqu6 +-----END CERTIFICATE----- diff --git a/examples/echo/ws-echo/client/profiles/dev/config.toml b/examples/echo/ws-echo/client/profiles/dev/config.toml new file mode 100644 index 00000000..e415e567 --- /dev/null +++ b/examples/echo/ws-echo/client/profiles/dev/config.toml @@ -0,0 +1,50 @@ +# toml configure file +# toml中key的首字母可以小写,但是对应的golang中的struct成员首字母必须大写 + +AppName = "ECHO-CLIENT" + +# host +LocalHost = "127.0.0.1" + +# server +# ServerHost = "127.0.0.1" +# ServerHost = "192.168.8.3" +ServerHost = "localhost" +ServerPort = 10000 +ServerPath = "/echo" +ProfilePort = 10080 + +# connection pool +# 连接池连接数目 +ConnectionNum = 2 + +# session +# client与server之间连接的心跳周期 +HeartbeatPeriod = "10s" +# client与server之间连接的超时时间 +SessionTimeout = "20s" + +# client +# client echo request string +# EchoString = "Hello, getty!" +# for compress test +EchoString = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +# 发送echo请求次数 +EchoTimes = 10 + +# app fail fast +FailFastTimeout = "3s" + +# tcp +[GettySessionParam] + CompressEncoding = true + TcpNoDelay = true + TcpKeepAlive = true + TcpRBufSize = 262144 + TcpWBufSize = 65536 + PkgWQSize = 256 + TcpReadTimeout = "1s" + TcpWriteTimeout = "5s" + WaitTimeout = "1s" + MaxMsgLen = 128 + SessionName = "echo-client" diff --git a/examples/echo/ws-echo/client/profiles/dev/log.xml b/examples/echo/ws-echo/client/profiles/dev/log.xml new file mode 100644 index 00000000..54ba0877 --- /dev/null +++ b/examples/echo/ws-echo/client/profiles/dev/log.xml @@ -0,0 +1,63 @@ + + + stdout + console + + INFO + + + debug_file + file + DEBUG + logs/debug.log + [%D %T] [%L] [%S] %M + true + 0M + 0K + true + + + info_file + file + INFO + logs/info.log + + [%D %T] [%L] [%S] %M + true + 0M + 0K + true + + + warn_file + file + WARNING + logs/warn.log + [%D %T] [%L] [%S] %M + true + 0M + 0K + true + + + error_file + file + ERROR + logs/error.log + [%D %T] [%L] [%S] %M + true + 0M + 0K + true + + diff --git a/examples/echo/ws-echo/client/profiles/release/cert/client.crt b/examples/echo/ws-echo/client/profiles/release/cert/client.crt new file mode 100644 index 00000000..cf747f3f --- /dev/null +++ b/examples/echo/ws-echo/client/profiles/release/cert/client.crt @@ -0,0 +1,14 @@ +-----BEGIN CERTIFICATE----- +MIICHjCCAYegAwIBAgIQKpKqamBqmZ0hfp8sYb4uNDANBgkqhkiG9w0BAQsFADAS +MRAwDgYDVQQKEwdBY21lIENvMCAXDTcwMDEwMTAwMDAwMFoYDzIwODQwMTI5MTYw +MDAwWjASMRAwDgYDVQQKEwdBY21lIENvMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCB +iQKBgQC5Nxsk6WjeaYazRYiGxHZ5G3FXSlSjV7lZeebItdEPzO8kVPIGCSTy/M5X +Nnpp3uVDFXQub0/O5t9Y6wcuqpUGMOV+XL7MZqSZlodXm0XhNYzCAjZ+URNjTHGP +NXIqdDEG5Ba8SXMOfY6H97+QxugZoAMFZ+N83ggr12IYNO/FbQIDAQABo3MwcTAO +BgNVHQ8BAf8EBAMCAqQwEwYDVR0lBAwwCgYIKwYBBQUHAwEwDwYDVR0TAQH/BAUw +AwEB/zA5BgNVHREEMjAwgglsb2NhbGhvc3SCC2V4YW1wbGUuY29thwR/AAABhxAA +AAAAAAAAAAAAAAAAAAABMA0GCSqGSIb3DQEBCwUAA4GBAE5dr9q7ORmKZ7yZqeSL +305armc13A7UxffUajeJFujpl2jOqnb5PuKJ7fn5HQKGB0qSq3IHsFua2WONXcTW +Vn4gS0k50IaDpW+yl+ArIo0QwbjPIAcFysX10p9dVO7A1uEpHbRDzefem6r9uVGk +i7dOLEoC8hkfk6nJsNEIEqu6 +-----END CERTIFICATE----- diff --git a/examples/echo/ws-echo/client/profiles/release/config.toml b/examples/echo/ws-echo/client/profiles/release/config.toml new file mode 100644 index 00000000..e415e567 --- /dev/null +++ b/examples/echo/ws-echo/client/profiles/release/config.toml @@ -0,0 +1,50 @@ +# toml configure file +# toml中key的首字母可以小写,但是对应的golang中的struct成员首字母必须大写 + +AppName = "ECHO-CLIENT" + +# host +LocalHost = "127.0.0.1" + +# server +# ServerHost = "127.0.0.1" +# ServerHost = "192.168.8.3" +ServerHost = "localhost" +ServerPort = 10000 +ServerPath = "/echo" +ProfilePort = 10080 + +# connection pool +# 连接池连接数目 +ConnectionNum = 2 + +# session +# client与server之间连接的心跳周期 +HeartbeatPeriod = "10s" +# client与server之间连接的超时时间 +SessionTimeout = "20s" + +# client +# client echo request string +# EchoString = "Hello, getty!" +# for compress test +EchoString = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +# 发送echo请求次数 +EchoTimes = 10 + +# app fail fast +FailFastTimeout = "3s" + +# tcp +[GettySessionParam] + CompressEncoding = true + TcpNoDelay = true + TcpKeepAlive = true + TcpRBufSize = 262144 + TcpWBufSize = 65536 + PkgWQSize = 256 + TcpReadTimeout = "1s" + TcpWriteTimeout = "5s" + WaitTimeout = "1s" + MaxMsgLen = 128 + SessionName = "echo-client" diff --git a/examples/echo/ws-echo/client/profiles/release/log.xml b/examples/echo/ws-echo/client/profiles/release/log.xml new file mode 100644 index 00000000..54ba0877 --- /dev/null +++ b/examples/echo/ws-echo/client/profiles/release/log.xml @@ -0,0 +1,63 @@ + + + stdout + console + + INFO + + + debug_file + file + DEBUG + logs/debug.log + [%D %T] [%L] [%S] %M + true + 0M + 0K + true + + + info_file + file + INFO + logs/info.log + + [%D %T] [%L] [%S] %M + true + 0M + 0K + true + + + warn_file + file + WARNING + logs/warn.log + [%D %T] [%L] [%S] %M + true + 0M + 0K + true + + + error_file + file + ERROR + logs/error.log + [%D %T] [%L] [%S] %M + true + 0M + 0K + true + + diff --git a/examples/echo/ws-echo/client/profiles/test/cert/client.crt b/examples/echo/ws-echo/client/profiles/test/cert/client.crt new file mode 100644 index 00000000..cf747f3f --- /dev/null +++ b/examples/echo/ws-echo/client/profiles/test/cert/client.crt @@ -0,0 +1,14 @@ +-----BEGIN CERTIFICATE----- +MIICHjCCAYegAwIBAgIQKpKqamBqmZ0hfp8sYb4uNDANBgkqhkiG9w0BAQsFADAS +MRAwDgYDVQQKEwdBY21lIENvMCAXDTcwMDEwMTAwMDAwMFoYDzIwODQwMTI5MTYw +MDAwWjASMRAwDgYDVQQKEwdBY21lIENvMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCB +iQKBgQC5Nxsk6WjeaYazRYiGxHZ5G3FXSlSjV7lZeebItdEPzO8kVPIGCSTy/M5X +Nnpp3uVDFXQub0/O5t9Y6wcuqpUGMOV+XL7MZqSZlodXm0XhNYzCAjZ+URNjTHGP +NXIqdDEG5Ba8SXMOfY6H97+QxugZoAMFZ+N83ggr12IYNO/FbQIDAQABo3MwcTAO +BgNVHQ8BAf8EBAMCAqQwEwYDVR0lBAwwCgYIKwYBBQUHAwEwDwYDVR0TAQH/BAUw +AwEB/zA5BgNVHREEMjAwgglsb2NhbGhvc3SCC2V4YW1wbGUuY29thwR/AAABhxAA +AAAAAAAAAAAAAAAAAAABMA0GCSqGSIb3DQEBCwUAA4GBAE5dr9q7ORmKZ7yZqeSL +305armc13A7UxffUajeJFujpl2jOqnb5PuKJ7fn5HQKGB0qSq3IHsFua2WONXcTW +Vn4gS0k50IaDpW+yl+ArIo0QwbjPIAcFysX10p9dVO7A1uEpHbRDzefem6r9uVGk +i7dOLEoC8hkfk6nJsNEIEqu6 +-----END CERTIFICATE----- diff --git a/examples/echo/ws-echo/client/profiles/test/config.toml b/examples/echo/ws-echo/client/profiles/test/config.toml new file mode 100644 index 00000000..e415e567 --- /dev/null +++ b/examples/echo/ws-echo/client/profiles/test/config.toml @@ -0,0 +1,50 @@ +# toml configure file +# toml中key的首字母可以小写,但是对应的golang中的struct成员首字母必须大写 + +AppName = "ECHO-CLIENT" + +# host +LocalHost = "127.0.0.1" + +# server +# ServerHost = "127.0.0.1" +# ServerHost = "192.168.8.3" +ServerHost = "localhost" +ServerPort = 10000 +ServerPath = "/echo" +ProfilePort = 10080 + +# connection pool +# 连接池连接数目 +ConnectionNum = 2 + +# session +# client与server之间连接的心跳周期 +HeartbeatPeriod = "10s" +# client与server之间连接的超时时间 +SessionTimeout = "20s" + +# client +# client echo request string +# EchoString = "Hello, getty!" +# for compress test +EchoString = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +# 发送echo请求次数 +EchoTimes = 10 + +# app fail fast +FailFastTimeout = "3s" + +# tcp +[GettySessionParam] + CompressEncoding = true + TcpNoDelay = true + TcpKeepAlive = true + TcpRBufSize = 262144 + TcpWBufSize = 65536 + PkgWQSize = 256 + TcpReadTimeout = "1s" + TcpWriteTimeout = "5s" + WaitTimeout = "1s" + MaxMsgLen = 128 + SessionName = "echo-client" diff --git a/examples/echo/ws-echo/client/profiles/test/log.xml b/examples/echo/ws-echo/client/profiles/test/log.xml new file mode 100644 index 00000000..54ba0877 --- /dev/null +++ b/examples/echo/ws-echo/client/profiles/test/log.xml @@ -0,0 +1,63 @@ + + + stdout + console + + INFO + + + debug_file + file + DEBUG + logs/debug.log + [%D %T] [%L] [%S] %M + true + 0M + 0K + true + + + info_file + file + INFO + logs/info.log + + [%D %T] [%L] [%S] %M + true + 0M + 0K + true + + + warn_file + file + WARNING + logs/warn.log + [%D %T] [%L] [%S] %M + true + 0M + 0K + true + + + error_file + file + ERROR + logs/error.log + [%D %T] [%L] [%S] %M + true + 0M + 0K + true + + diff --git a/examples/echo/ws-echo/js-client/addr.js b/examples/echo/ws-echo/js-client/addr.js new file mode 100644 index 00000000..af3ca2aa --- /dev/null +++ b/examples/echo/ws-echo/js-client/addr.js @@ -0,0 +1,3 @@ + // var serverAddress = '192.168.8.3:10000'; + var serverAddress = '127.0.0.1:10000'; + // var serverAddress = 'localhost:10000'; diff --git a/examples/echo/ws-echo/js-client/encoding.js b/examples/echo/ws-echo/js-client/encoding.js new file mode 100644 index 00000000..c6f5f8aa --- /dev/null +++ b/examples/echo/ws-echo/js-client/encoding.js @@ -0,0 +1,3095 @@ +// This is free and unencumbered software released into the public domain. +// See LICENSE.md for more information. + +// If we're in node require encoding-indexes and attach it to the global. +/** + * @fileoverview Global |this| required for resolving indexes in node. + * @suppress {globalThis} + */ +if (typeof module !== "undefined" && module.exports) { + this["encoding-indexes"] = + require("./encoding-indexes.js")["encoding-indexes"]; +} + +(function(global) { + 'use strict'; + + // + // Utilities + // + + /** + * @param {number} a The number to test. + * @param {number} min The minimum value in the range, inclusive. + * @param {number} max The maximum value in the range, inclusive. + * @return {boolean} True if a >= min and a <= max. + */ + function inRange(a, min, max) { + return min <= a && a <= max; + } + + /** + * @param {number} n The numerator. + * @param {number} d The denominator. + * @return {number} The result of the integer division of n by d. + */ + function div(n, d) { + return Math.floor(n / d); + } + + /** + * @param {*} o + * @return {Object} + */ + function ToDictionary(o) { + if (o === undefined) return {}; + if (o === Object(o)) return o; + throw TypeError('Could not convert argument to dictionary'); + } + + /** + * @param {string} string Input string of UTF-16 code units. + * @return {!Array.} Code points. + */ + function stringToCodePoints(string) { + // https://heycam.github.io/webidl/#dfn-obtain-unicode + + // 1. Let S be the DOMString value. + var s = String(string); + + // 2. Let n be the length of S. + var n = s.length; + + // 3. Initialize i to 0. + var i = 0; + + // 4. Initialize U to be an empty sequence of Unicode characters. + var u = []; + + // 5. While i < n: + while (i < n) { + + // 1. Let c be the code unit in S at index i. + var c = s.charCodeAt(i); + + // 2. Depending on the value of c: + + // c < 0xD800 or c > 0xDFFF + if (c < 0xD800 || c > 0xDFFF) { + // Append to U the Unicode character with code point c. + u.push(c); + } + + // 0xDC00 ≤ c ≤ 0xDFFF + else if (0xDC00 <= c && c <= 0xDFFF) { + // Append to U a U+FFFD REPLACEMENT CHARACTER. + u.push(0xFFFD); + } + + // 0xD800 ≤ c ≤ 0xDBFF + else if (0xD800 <= c && c <= 0xDBFF) { + // 1. If i = n−1, then append to U a U+FFFD REPLACEMENT + // CHARACTER. + if (i === n - 1) { + u.push(0xFFFD); + } + // 2. Otherwise, i < n−1: + else { + // 1. Let d be the code unit in S at index i+1. + var d = string.charCodeAt(i + 1); + + // 2. If 0xDC00 ≤ d ≤ 0xDFFF, then: + if (0xDC00 <= d && d <= 0xDFFF) { + // 1. Let a be c & 0x3FF. + var a = c & 0x3FF; + + // 2. Let b be d & 0x3FF. + var b = d & 0x3FF; + + // 3. Append to U the Unicode character with code point + // 2^16+2^10*a+b. + u.push(0x10000 + (a << 10) + b); + + // 4. Set i to i+1. + i += 1; + } + + // 3. Otherwise, d < 0xDC00 or d > 0xDFFF. Append to U a + // U+FFFD REPLACEMENT CHARACTER. + else { + u.push(0xFFFD); + } + } + } + + // 3. Set i to i+1. + i += 1; + } + + // 6. Return U. + return u; + } + + /** + * @param {!Array.} code_points Array of code points. + * @return {string} string String of UTF-16 code units. + */ + function codePointsToString(code_points) { + var s = ''; + for (var i = 0; i < code_points.length; ++i) { + var cp = code_points[i]; + if (cp <= 0xFFFF) { + s += String.fromCharCode(cp); + } else { + cp -= 0x10000; + s += String.fromCharCode((cp >> 10) + 0xD800, + (cp & 0x3FF) + 0xDC00); + } + } + return s; + } + + + // + // Implementation of Encoding specification + // https://encoding.spec.whatwg.org/ + // + + // + // 3. Terminology + // + + /** + * End-of-stream is a special token that signifies no more tokens + * are in the stream. + * @const + */ var end_of_stream = -1; + + /** + * A stream represents an ordered sequence of tokens. + * + * @constructor + * @param {!(Array.|Uint8Array)} tokens Array of tokens that provide the + * stream. + */ + function Stream(tokens) { + /** @type {!Array.} */ + this.tokens = [].slice.call(tokens); + } + + Stream.prototype = { + /** + * @return {boolean} True if end-of-stream has been hit. + */ + endOfStream: function() { + return !this.tokens.length; + }, + + /** + * When a token is read from a stream, the first token in the + * stream must be returned and subsequently removed, and + * end-of-stream must be returned otherwise. + * + * @return {number} Get the next token from the stream, or + * end_of_stream. + */ + read: function() { + if (!this.tokens.length) + return end_of_stream; + return this.tokens.shift(); + }, + + /** + * When one or more tokens are prepended to a stream, those tokens + * must be inserted, in given order, before the first token in the + * stream. + * + * @param {(number|!Array.)} token The token(s) to prepend to the stream. + */ + prepend: function(token) { + if (Array.isArray(token)) { + var tokens = /**@type {!Array.}*/(token); + while (tokens.length) + this.tokens.unshift(tokens.pop()); + } else { + this.tokens.unshift(token); + } + }, + + /** + * When one or more tokens are pushed to a stream, those tokens + * must be inserted, in given order, after the last token in the + * stream. + * + * @param {(number|!Array.)} token The tokens(s) to prepend to the stream. + */ + push: function(token) { + if (Array.isArray(token)) { + var tokens = /**@type {!Array.}*/(token); + while (tokens.length) + this.tokens.push(tokens.shift()); + } else { + this.tokens.push(token); + } + } + }; + + // + // 4. Encodings + // + + // 4.1 Encoders and decoders + + /** @const */ + var finished = -1; + + /** + * @param {boolean} fatal If true, decoding errors raise an exception. + * @param {number=} opt_code_point Override the standard fallback code point. + * @return {number} The code point to insert on a decoding error. + */ + function decoderError(fatal, opt_code_point) { + if (fatal) + throw TypeError('Decoder error'); + return opt_code_point || 0xFFFD; + } + + /** + * @param {number} code_point The code point that could not be encoded. + * @return {number} Always throws, no value is actually returned. + */ + function encoderError(code_point) { + throw TypeError('The code point ' + code_point + ' could not be encoded.'); + } + + /** @interface */ + function Decoder() {} + Decoder.prototype = { + /** + * @param {Stream} stream The stream of bytes being decoded. + * @param {number} bite The next byte read from the stream. + * @return {?(number|!Array.)} The next code point(s) + * decoded, or null if not enough data exists in the input + * stream to decode a complete code point, or |finished|. + */ + handler: function(stream, bite) {} + }; + + /** @interface */ + function Encoder() {} + Encoder.prototype = { + /** + * @param {Stream} stream The stream of code points being encoded. + * @param {number} code_point Next code point read from the stream. + * @return {(number|!Array.)} Byte(s) to emit, or |finished|. + */ + handler: function(stream, code_point) {} + }; + + // 4.2 Names and labels + + // TODO: Define @typedef for Encoding: {name:string,labels:Array.} + // https://github.com/google/closure-compiler/issues/247 + + /** + * @param {string} label The encoding label. + * @return {?{name:string,labels:Array.}} + */ + function getEncoding(label) { + // 1. Remove any leading and trailing ASCII whitespace from label. + label = String(label).trim().toLowerCase(); + + // 2. If label is an ASCII case-insensitive match for any of the + // labels listed in the table below, return the corresponding + // encoding, and failure otherwise. + if (Object.prototype.hasOwnProperty.call(label_to_encoding, label)) { + return label_to_encoding[label]; + } + return null; + } + + /** + * Encodings table: https://encoding.spec.whatwg.org/encodings.json + * @const + * @type {!Array.<{ + * heading: string, + * encodings: Array.<{name:string,labels:Array.}> + * }>} + */ + var encodings = [ + { + "encodings": [ + { + "labels": [ + "unicode-1-1-utf-8", + "utf-8", + "utf8" + ], + "name": "utf-8" + } + ], + "heading": "The Encoding" + }, + { + "encodings": [ + { + "labels": [ + "866", + "cp866", + "csibm866", + "ibm866" + ], + "name": "ibm866" + }, + { + "labels": [ + "csisolatin2", + "iso-8859-2", + "iso-ir-101", + "iso8859-2", + "iso88592", + "iso_8859-2", + "iso_8859-2:1987", + "l2", + "latin2" + ], + "name": "iso-8859-2" + }, + { + "labels": [ + "csisolatin3", + "iso-8859-3", + "iso-ir-109", + "iso8859-3", + "iso88593", + "iso_8859-3", + "iso_8859-3:1988", + "l3", + "latin3" + ], + "name": "iso-8859-3" + }, + { + "labels": [ + "csisolatin4", + "iso-8859-4", + "iso-ir-110", + "iso8859-4", + "iso88594", + "iso_8859-4", + "iso_8859-4:1988", + "l4", + "latin4" + ], + "name": "iso-8859-4" + }, + { + "labels": [ + "csisolatincyrillic", + "cyrillic", + "iso-8859-5", + "iso-ir-144", + "iso8859-5", + "iso88595", + "iso_8859-5", + "iso_8859-5:1988" + ], + "name": "iso-8859-5" + }, + { + "labels": [ + "arabic", + "asmo-708", + "csiso88596e", + "csiso88596i", + "csisolatinarabic", + "ecma-114", + "iso-8859-6", + "iso-8859-6-e", + "iso-8859-6-i", + "iso-ir-127", + "iso8859-6", + "iso88596", + "iso_8859-6", + "iso_8859-6:1987" + ], + "name": "iso-8859-6" + }, + { + "labels": [ + "csisolatingreek", + "ecma-118", + "elot_928", + "greek", + "greek8", + "iso-8859-7", + "iso-ir-126", + "iso8859-7", + "iso88597", + "iso_8859-7", + "iso_8859-7:1987", + "sun_eu_greek" + ], + "name": "iso-8859-7" + }, + { + "labels": [ + "csiso88598e", + "csisolatinhebrew", + "hebrew", + "iso-8859-8", + "iso-8859-8-e", + "iso-ir-138", + "iso8859-8", + "iso88598", + "iso_8859-8", + "iso_8859-8:1988", + "visual" + ], + "name": "iso-8859-8" + }, + { + "labels": [ + "csiso88598i", + "iso-8859-8-i", + "logical" + ], + "name": "iso-8859-8-i" + }, + { + "labels": [ + "csisolatin6", + "iso-8859-10", + "iso-ir-157", + "iso8859-10", + "iso885910", + "l6", + "latin6" + ], + "name": "iso-8859-10" + }, + { + "labels": [ + "iso-8859-13", + "iso8859-13", + "iso885913" + ], + "name": "iso-8859-13" + }, + { + "labels": [ + "iso-8859-14", + "iso8859-14", + "iso885914" + ], + "name": "iso-8859-14" + }, + { + "labels": [ + "csisolatin9", + "iso-8859-15", + "iso8859-15", + "iso885915", + "iso_8859-15", + "l9" + ], + "name": "iso-8859-15" + }, + { + "labels": [ + "iso-8859-16" + ], + "name": "iso-8859-16" + }, + { + "labels": [ + "cskoi8r", + "koi", + "koi8", + "koi8-r", + "koi8_r" + ], + "name": "koi8-r" + }, + { + "labels": [ + "koi8-ru", + "koi8-u" + ], + "name": "koi8-u" + }, + { + "labels": [ + "csmacintosh", + "mac", + "macintosh", + "x-mac-roman" + ], + "name": "macintosh" + }, + { + "labels": [ + "dos-874", + "iso-8859-11", + "iso8859-11", + "iso885911", + "tis-620", + "windows-874" + ], + "name": "windows-874" + }, + { + "labels": [ + "cp1250", + "windows-1250", + "x-cp1250" + ], + "name": "windows-1250" + }, + { + "labels": [ + "cp1251", + "windows-1251", + "x-cp1251" + ], + "name": "windows-1251" + }, + { + "labels": [ + "ansi_x3.4-1968", + "ascii", + "cp1252", + "cp819", + "csisolatin1", + "ibm819", + "iso-8859-1", + "iso-ir-100", + "iso8859-1", + "iso88591", + "iso_8859-1", + "iso_8859-1:1987", + "l1", + "latin1", + "us-ascii", + "windows-1252", + "x-cp1252" + ], + "name": "windows-1252" + }, + { + "labels": [ + "cp1253", + "windows-1253", + "x-cp1253" + ], + "name": "windows-1253" + }, + { + "labels": [ + "cp1254", + "csisolatin5", + "iso-8859-9", + "iso-ir-148", + "iso8859-9", + "iso88599", + "iso_8859-9", + "iso_8859-9:1989", + "l5", + "latin5", + "windows-1254", + "x-cp1254" + ], + "name": "windows-1254" + }, + { + "labels": [ + "cp1255", + "windows-1255", + "x-cp1255" + ], + "name": "windows-1255" + }, + { + "labels": [ + "cp1256", + "windows-1256", + "x-cp1256" + ], + "name": "windows-1256" + }, + { + "labels": [ + "cp1257", + "windows-1257", + "x-cp1257" + ], + "name": "windows-1257" + }, + { + "labels": [ + "cp1258", + "windows-1258", + "x-cp1258" + ], + "name": "windows-1258" + }, + { + "labels": [ + "x-mac-cyrillic", + "x-mac-ukrainian" + ], + "name": "x-mac-cyrillic" + } + ], + "heading": "Legacy single-byte encodings" + }, + { + "encodings": [ + { + "labels": [ + "chinese", + "csgb2312", + "csiso58gb231280", + "gb2312", + "gb_2312", + "gb_2312-80", + "gbk", + "iso-ir-58", + "x-gbk" + ], + "name": "gbk" + }, + { + "labels": [ + "gb18030" + ], + "name": "gb18030" + } + ], + "heading": "Legacy multi-byte Chinese (simplified) encodings" + }, + { + "encodings": [ + { + "labels": [ + "big5", + "big5-hkscs", + "cn-big5", + "csbig5", + "x-x-big5" + ], + "name": "big5" + } + ], + "heading": "Legacy multi-byte Chinese (traditional) encodings" + }, + { + "encodings": [ + { + "labels": [ + "cseucpkdfmtjapanese", + "euc-jp", + "x-euc-jp" + ], + "name": "euc-jp" + }, + { + "labels": [ + "csiso2022jp", + "iso-2022-jp" + ], + "name": "iso-2022-jp" + }, + { + "labels": [ + "csshiftjis", + "ms932", + "ms_kanji", + "shift-jis", + "shift_jis", + "sjis", + "windows-31j", + "x-sjis" + ], + "name": "shift_jis" + } + ], + "heading": "Legacy multi-byte Japanese encodings" + }, + { + "encodings": [ + { + "labels": [ + "cseuckr", + "csksc56011987", + "euc-kr", + "iso-ir-149", + "korean", + "ks_c_5601-1987", + "ks_c_5601-1989", + "ksc5601", + "ksc_5601", + "windows-949" + ], + "name": "euc-kr" + } + ], + "heading": "Legacy multi-byte Korean encodings" + }, + { + "encodings": [ + { + "labels": [ + "csiso2022kr", + "hz-gb-2312", + "iso-2022-cn", + "iso-2022-cn-ext", + "iso-2022-kr" + ], + "name": "replacement" + }, + { + "labels": [ + "utf-16be" + ], + "name": "utf-16be" + }, + { + "labels": [ + "utf-16", + "utf-16le" + ], + "name": "utf-16le" + }, + { + "labels": [ + "x-user-defined" + ], + "name": "x-user-defined" + } + ], + "heading": "Legacy miscellaneous encodings" + } + ]; + + // Label to encoding registry. + /** @type {Object.}>} */ + var label_to_encoding = {}; + encodings.forEach(function(category) { + category.encodings.forEach(function(encoding) { + encoding.labels.forEach(function(label) { + label_to_encoding[label] = encoding; + }); + }); + }); + + // Registry of of encoder/decoder factories, by encoding name. + /** @type {Object.} */ + var encoders = {}; + /** @type {Object.} */ + var decoders = {}; + + // + // 5. Indexes + // + + /** + * @param {number} pointer The |pointer| to search for. + * @param {(!Array.|undefined)} index The |index| to search within. + * @return {?number} The code point corresponding to |pointer| in |index|, + * or null if |code point| is not in |index|. + */ + function indexCodePointFor(pointer, index) { + if (!index) return null; + return index[pointer] || null; + } + + /** + * @param {number} code_point The |code point| to search for. + * @param {!Array.} index The |index| to search within. + * @return {?number} The first pointer corresponding to |code point| in + * |index|, or null if |code point| is not in |index|. + */ + function indexPointerFor(code_point, index) { + var pointer = index.indexOf(code_point); + return pointer === -1 ? null : pointer; + } + + /** + * @param {string} name Name of the index. + * @return {(!Array.|!Array.>)} + * */ + function index(name) { + if (!('encoding-indexes' in global)) { + throw Error("Indexes missing." + + " Did you forget to include encoding-indexes.js?"); + } + return global['encoding-indexes'][name]; + } + + /** + * @param {number} pointer The |pointer| to search for in the gb18030 index. + * @return {?number} The code point corresponding to |pointer| in |index|, + * or null if |code point| is not in the gb18030 index. + */ + function indexGB18030RangesCodePointFor(pointer) { + // 1. If pointer is greater than 39419 and less than 189000, or + // pointer is greater than 1237575, return null. + if ((pointer > 39419 && pointer < 189000) || (pointer > 1237575)) + return null; + + // 2. Let offset be the last pointer in index gb18030 ranges that + // is equal to or less than pointer and let code point offset be + // its corresponding code point. + var offset = 0; + var code_point_offset = 0; + var idx = index('gb18030'); + var i; + for (i = 0; i < idx.length; ++i) { + /** @type {!Array.} */ + var entry = idx[i]; + if (entry[0] <= pointer) { + offset = entry[0]; + code_point_offset = entry[1]; + } else { + break; + } + } + + // 3. Return a code point whose value is code point offset + + // pointer − offset. + return code_point_offset + pointer - offset; + } + + /** + * @param {number} code_point The |code point| to locate in the gb18030 index. + * @return {number} The first pointer corresponding to |code point| in the + * gb18030 index. + */ + function indexGB18030RangesPointerFor(code_point) { + // 1. Let offset be the last code point in index gb18030 ranges + // that is equal to or less than code point and let pointer offset + // be its corresponding pointer. + var offset = 0; + var pointer_offset = 0; + var idx = index('gb18030'); + var i; + for (i = 0; i < idx.length; ++i) { + /** @type {!Array.} */ + var entry = idx[i]; + if (entry[1] <= code_point) { + offset = entry[1]; + pointer_offset = entry[0]; + } else { + break; + } + } + + // 2. Return a pointer whose value is pointer offset + code point + // − offset. + return pointer_offset + code_point - offset; + } + + /** + * @param {number} code_point The |code_point| to search for in the shift_jis index. + * @return {?number} The code point corresponding to |pointer| in |index|, + * or null if |code point| is not in the shift_jis index. + */ + function indexShiftJISPointerFor(code_point) { + // 1. Let index be index jis0208 excluding all pointers in the + // range 8272 to 8835. + var pointer = indexPointerFor(code_point, index('jis0208')); + if (pointer === null || inRange(pointer, 8272, 8835)) + return null; + + // 2. Return the index pointer for code point in index. + return pointer; + } + + /** + * @param {number} code_point The |code_point| to search for in the big5 index. + * @return {?number} The code point corresponding to |pointer| in |index|, + * or null if |code point| is not in the big5 index. + */ + function indexBig5PointerFor(code_point) { + + // 1. Let index be index big5. + var index_ = index('big5'); + + // 2. If code point is U+2550, U+255E, U+2561, U+256A, U+5341, or + // U+5345, return the last pointer corresponding to code point in + // index. + if (code_point === 0x2550 || code_point === 0x255E || + code_point === 0x2561 || code_point === 0x256A || + code_point === 0x5341 || code_point === 0x5345) { + return index.lastIndexOf(code_point); + } + + // 3. Return the index pointer for code point in index. + return indexPointerFor(code_point, index_); + } + + // + // 7. API + // + + /** @const */ var DEFAULT_ENCODING = 'utf-8'; + + // 7.1 Interface TextDecoder + + /** + * @constructor + * @param {string=} encoding The label of the encoding; + * defaults to 'utf-8'. + * @param {Object=} options + */ + function TextDecoder(encoding, options) { + if (!(this instanceof TextDecoder)) { + return new TextDecoder(encoding, options); + } + encoding = encoding !== undefined ? String(encoding) : DEFAULT_ENCODING; + options = ToDictionary(options); + /** @private */ + this._encoding = getEncoding(encoding); + if (this._encoding === null || this._encoding.name === 'replacement') + throw RangeError('Unknown encoding: ' + encoding); + + if (!decoders[this._encoding.name]) { + throw Error('Decoder not present.' + + ' Did you forget to include encoding-indexes.js?'); + } + + /** @private @type {boolean} */ + this._streaming = false; + /** @private @type {boolean} */ + this._BOMseen = false; + /** @private @type {?Decoder} */ + this._decoder = null; + /** @private @type {boolean} */ + this._fatal = Boolean(options['fatal']); + /** @private @type {boolean} */ + this._ignoreBOM = Boolean(options['ignoreBOM']); + + if (Object.defineProperty) { + Object.defineProperty(this, 'encoding', {value: this._encoding.name}); + Object.defineProperty(this, 'fatal', {value: this._fatal}); + Object.defineProperty(this, 'ignoreBOM', {value: this._ignoreBOM}); + } else { + this.encoding = this._encoding.name; + this.fatal = this._fatal; + this.ignoreBOM = this._ignoreBOM; + } + + return this; + } + + TextDecoder.prototype = { + /** + * @param {ArrayBufferView=} input The buffer of bytes to decode. + * @param {Object=} options + * @return {string} The decoded string. + */ + decode: function decode(input, options) { + var bytes; + if (typeof input === 'object' && input instanceof ArrayBuffer) { + bytes = new Uint8Array(input); + } else if (typeof input === 'object' && 'buffer' in input && + input.buffer instanceof ArrayBuffer) { + bytes = new Uint8Array(input.buffer, + input.byteOffset, + input.byteLength); + } else { + bytes = new Uint8Array(0); + } + + options = ToDictionary(options); + + if (!this._streaming) { + this._decoder = decoders[this._encoding.name]({fatal: this._fatal}); + this._BOMseen = false; + } + this._streaming = Boolean(options['stream']); + + var input_stream = new Stream(bytes); + + var code_points = []; + + /** @type {?(number|!Array.)} */ + var result; + + while (!input_stream.endOfStream()) { + result = this._decoder.handler(input_stream, input_stream.read()); + if (result === finished) + break; + if (result === null) + continue; + if (Array.isArray(result)) + code_points.push.apply(code_points, /**@type {!Array.}*/(result)); + else + code_points.push(result); + } + if (!this._streaming) { + do { + result = this._decoder.handler(input_stream, input_stream.read()); + if (result === finished) + break; + if (result === null) + continue; + if (Array.isArray(result)) + code_points.push.apply(code_points, /**@type {!Array.}*/(result)); + else + code_points.push(result); + } while (!input_stream.endOfStream()); + this._decoder = null; + } + + if (code_points.length) { + // If encoding is one of utf-8, utf-16be, and utf-16le, and + // ignore BOM flag and BOM seen flag are unset, run these + // subsubsteps: + if (['utf-8', 'utf-16le', 'utf-16be'].indexOf(this.encoding) !== -1 && + !this._ignoreBOM && !this._BOMseen) { + // If token is U+FEFF, set BOM seen flag. + if (code_points[0] === 0xFEFF) { + this._BOMseen = true; + code_points.shift(); + } else { + // Otherwise, if token is not end-of-stream, set BOM seen + // flag and append token to output. + this._BOMseen = true; + } + } + } + + return codePointsToString(code_points); + } + }; + + // 7.2 Interface TextEncoder + + /** + * @constructor + * @param {string=} encoding The label of the encoding; + * defaults to 'utf-8'. + * @param {Object=} options + */ + function TextEncoder(encoding, options) { + if (!(this instanceof TextEncoder)) + return new TextEncoder(encoding, options); + encoding = encoding !== undefined ? String(encoding) : DEFAULT_ENCODING; + options = ToDictionary(options); + /** @private */ + this._encoding = getEncoding(encoding); + if (this._encoding === null || this._encoding.name === 'replacement') + throw RangeError('Unknown encoding: ' + encoding); + + var allowLegacyEncoding = + Boolean(options['NONSTANDARD_allowLegacyEncoding']); + var isLegacyEncoding = (this._encoding.name !== 'utf-8' && + this._encoding.name !== 'utf-16le' && + this._encoding.name !== 'utf-16be'); + if (this._encoding === null || (isLegacyEncoding && !allowLegacyEncoding)) + throw RangeError('Unknown encoding: ' + encoding); + + if (!encoders[this._encoding.name]) { + throw Error('Encoder not present.' + + ' Did you forget to include encoding-indexes.js?'); + } + + /** @private @type {boolean} */ + this._streaming = false; + /** @private @type {?Encoder} */ + this._encoder = null; + /** @private @type {{fatal: boolean}} */ + this._options = {fatal: Boolean(options['fatal'])}; + + if (Object.defineProperty) + Object.defineProperty(this, 'encoding', {value: this._encoding.name}); + else + this.encoding = this._encoding.name; + + return this; + } + + TextEncoder.prototype = { + /** + * @param {string=} opt_string The string to encode. + * @param {Object=} options + * @return {Uint8Array} Encoded bytes, as a Uint8Array. + */ + encode: function encode(opt_string, options) { + opt_string = opt_string ? String(opt_string) : ''; + options = ToDictionary(options); + + // NOTE: This option is nonstandard. None of the encodings + // permitted for encoding (i.e. UTF-8, UTF-16) are stateful, + // so streaming is not necessary. + if (!this._streaming) + this._encoder = encoders[this._encoding.name](this._options); + this._streaming = Boolean(options['stream']); + + var bytes = []; + var input_stream = new Stream(stringToCodePoints(opt_string)); + /** @type {?(number|!Array.)} */ + var result; + while (!input_stream.endOfStream()) { + result = this._encoder.handler(input_stream, input_stream.read()); + if (result === finished) + break; + if (Array.isArray(result)) + bytes.push.apply(bytes, /**@type {!Array.}*/(result)); + else + bytes.push(result); + } + if (!this._streaming) { + while (true) { + result = this._encoder.handler(input_stream, input_stream.read()); + if (result === finished) + break; + if (Array.isArray(result)) + bytes.push.apply(bytes, /**@type {!Array.}*/(result)); + else + bytes.push(result); + } + this._encoder = null; + } + return new Uint8Array(bytes); + } + }; + + + // + // 8. The encoding + // + + // 8.1 utf-8 + + /** + * @constructor + * @implements {Decoder} + * @param {{fatal: boolean}} options + */ + function UTF8Decoder(options) { + var fatal = options.fatal; + + // utf-8's decoder's has an associated utf-8 code point, utf-8 + // bytes seen, and utf-8 bytes needed (all initially 0), a utf-8 + // lower boundary (initially 0x80), and a utf-8 upper boundary + // (initially 0xBF). + var /** @type {number} */ utf8_code_point = 0, + /** @type {number} */ utf8_bytes_seen = 0, + /** @type {number} */ utf8_bytes_needed = 0, + /** @type {number} */ utf8_lower_boundary = 0x80, + /** @type {number} */ utf8_upper_boundary = 0xBF; + + /** + * @param {Stream} stream The stream of bytes being decoded. + * @param {number} bite The next byte read from the stream. + * @return {?(number|!Array.)} The next code point(s) + * decoded, or null if not enough data exists in the input + * stream to decode a complete code point. + */ + this.handler = function(stream, bite) { + // 1. If byte is end-of-stream and utf-8 bytes needed is not 0, + // set utf-8 bytes needed to 0 and return error. + if (bite === end_of_stream && utf8_bytes_needed !== 0) { + utf8_bytes_needed = 0; + return decoderError(fatal); + } + + // 2. If byte is end-of-stream, return finished. + if (bite === end_of_stream) + return finished; + + // 3. If utf-8 bytes needed is 0, based on byte: + if (utf8_bytes_needed === 0) { + + // 0x00 to 0x7F + if (inRange(bite, 0x00, 0x7F)) { + // Return a code point whose value is byte. + return bite; + } + + // 0xC2 to 0xDF + if (inRange(bite, 0xC2, 0xDF)) { + // Set utf-8 bytes needed to 1 and utf-8 code point to byte + // − 0xC0. + utf8_bytes_needed = 1; + utf8_code_point = bite - 0xC0; + } + + // 0xE0 to 0xEF + else if (inRange(bite, 0xE0, 0xEF)) { + // 1. If byte is 0xE0, set utf-8 lower boundary to 0xA0. + if (bite === 0xE0) + utf8_lower_boundary = 0xA0; + // 2. If byte is 0xED, set utf-8 upper boundary to 0x9F. + if (bite === 0xED) + utf8_upper_boundary = 0x9F; + // 3. Set utf-8 bytes needed to 2 and utf-8 code point to + // byte − 0xE0. + utf8_bytes_needed = 2; + utf8_code_point = bite - 0xE0; + } + + // 0xF0 to 0xF4 + else if (inRange(bite, 0xF0, 0xF4)) { + // 1. If byte is 0xF0, set utf-8 lower boundary to 0x90. + if (bite === 0xF0) + utf8_lower_boundary = 0x90; + // 2. If byte is 0xF4, set utf-8 upper boundary to 0x8F. + if (bite === 0xF4) + utf8_upper_boundary = 0x8F; + // 3. Set utf-8 bytes needed to 3 and utf-8 code point to + // byte − 0xF0. + utf8_bytes_needed = 3; + utf8_code_point = bite - 0xF0; + } + + // Otherwise + else { + // Return error. + return decoderError(fatal); + } + + // Then (byte is in the range 0xC2 to 0xF4) set utf-8 code + // point to utf-8 code point << (6 × utf-8 bytes needed) and + // return continue. + utf8_code_point = utf8_code_point << (6 * utf8_bytes_needed); + return null; + } + + // 4. If byte is not in the range utf-8 lower boundary to utf-8 + // upper boundary, run these substeps: + if (!inRange(bite, utf8_lower_boundary, utf8_upper_boundary)) { + + // 1. Set utf-8 code point, utf-8 bytes needed, and utf-8 + // bytes seen to 0, set utf-8 lower boundary to 0x80, and set + // utf-8 upper boundary to 0xBF. + utf8_code_point = utf8_bytes_needed = utf8_bytes_seen = 0; + utf8_lower_boundary = 0x80; + utf8_upper_boundary = 0xBF; + + // 2. Prepend byte to stream. + stream.prepend(bite); + + // 3. Return error. + return decoderError(fatal); + } + + // 5. Set utf-8 lower boundary to 0x80 and utf-8 upper boundary + // to 0xBF. + utf8_lower_boundary = 0x80; + utf8_upper_boundary = 0xBF; + + // 6. Increase utf-8 bytes seen by one and set utf-8 code point + // to utf-8 code point + (byte − 0x80) << (6 × (utf-8 bytes + // needed − utf-8 bytes seen)). + utf8_bytes_seen += 1; + utf8_code_point += (bite - 0x80) << (6 * (utf8_bytes_needed - utf8_bytes_seen)); + + // 7. If utf-8 bytes seen is not equal to utf-8 bytes needed, + // continue. + if (utf8_bytes_seen !== utf8_bytes_needed) + return null; + + // 8. Let code point be utf-8 code point. + var code_point = utf8_code_point; + + // 9. Set utf-8 code point, utf-8 bytes needed, and utf-8 bytes + // seen to 0. + utf8_code_point = utf8_bytes_needed = utf8_bytes_seen = 0; + + // 10. Return a code point whose value is code point. + return code_point; + }; + } + + /** + * @constructor + * @implements {Encoder} + * @param {{fatal: boolean}} options + */ + function UTF8Encoder(options) { + var fatal = options.fatal; + /** + * @param {Stream} stream Input stream. + * @param {number} code_point Next code point read from the stream. + * @return {(number|!Array.)} Byte(s) to emit. + */ + this.handler = function(stream, code_point) { + // 1. If code point is end-of-stream, return finished. + if (code_point === end_of_stream) + return finished; + + // 2. If code point is in the range U+0000 to U+007F, return a + // byte whose value is code point. + if (inRange(code_point, 0x0000, 0x007f)) + return code_point; + + // 3. Set count and offset based on the range code point is in: + var count, offset; + // U+0080 to U+07FF: 1 and 0xC0 + if (inRange(code_point, 0x0080, 0x07FF)) { + count = 1; + offset = 0xC0; + } + // U+0800 to U+FFFF: 2 and 0xE0 + else if (inRange(code_point, 0x0800, 0xFFFF)) { + count = 2; + offset = 0xE0; + } + // U+10000 to U+10FFFF: 3 and 0xF0 + else if (inRange(code_point, 0x10000, 0x10FFFF)) { + count = 3; + offset = 0xF0; + } + + // 4.Let bytes be a byte sequence whose first byte is (code + // point >> (6 × count)) + offset. + var bytes = [(code_point >> (6 * count)) + offset]; + + // 5. Run these substeps while count is greater than 0: + while (count > 0) { + + // 1. Set temp to code point >> (6 × (count − 1)). + var temp = code_point >> (6 * (count - 1)); + + // 2. Append to bytes 0x80 | (temp & 0x3F). + bytes.push(0x80 | (temp & 0x3F)); + + // 3. Decrease count by one. + count -= 1; + } + + // 6. Return bytes bytes, in order. + return bytes; + }; + } + + /** @param {{fatal: boolean}} options */ + encoders['utf-8'] = function(options) { + return new UTF8Encoder(options); + }; + /** @param {{fatal: boolean}} options */ + decoders['utf-8'] = function(options) { + return new UTF8Decoder(options); + }; + + // + // 9. Legacy single-byte encodings + // + + // 9.1 single-byte decoder + /** + * @constructor + * @implements {Decoder} + * @param {!Array.} index The encoding index. + * @param {{fatal: boolean}} options + */ + function SingleByteDecoder(index, options) { + var fatal = options.fatal; + /** + * @param {Stream} stream The stream of bytes being decoded. + * @param {number} bite The next byte read from the stream. + * @return {?(number|!Array.)} The next code point(s) + * decoded, or null if not enough data exists in the input + * stream to decode a complete code point. + */ + this.handler = function(stream, bite) { + // 1. If byte is end-of-stream, return finished. + if (bite === end_of_stream) + return finished; + + // 2. If byte is in the range 0x00 to 0x7F, return a code point + // whose value is byte. + if (inRange(bite, 0x00, 0x7F)) + return bite; + + // 3. Let code point be the index code point for byte − 0x80 in + // index single-byte. + var code_point = index[bite - 0x80]; + + // 4. If code point is null, return error. + if (code_point === null) + return decoderError(fatal); + + // 5. Return a code point whose value is code point. + return code_point; + }; + } + + // 9.2 single-byte encoder + /** + * @constructor + * @implements {Encoder} + * @param {!Array.} index The encoding index. + * @param {{fatal: boolean}} options + */ + function SingleByteEncoder(index, options) { + var fatal = options.fatal; + /** + * @param {Stream} stream Input stream. + * @param {number} code_point Next code point read from the stream. + * @return {(number|!Array.)} Byte(s) to emit. + */ + this.handler = function(stream, code_point) { + // 1. If code point is end-of-stream, return finished. + if (code_point === end_of_stream) + return finished; + + // 2. If code point is in the range U+0000 to U+007F, return a + // byte whose value is code point. + if (inRange(code_point, 0x0000, 0x007F)) + return code_point; + + // 3. Let pointer be the index pointer for code point in index + // single-byte. + var pointer = indexPointerFor(code_point, index); + + // 4. If pointer is null, return error with code point. + if (pointer === null) + encoderError(code_point); + + // 5. Return a byte whose value is pointer + 0x80. + return pointer + 0x80; + }; + } + + (function() { + if (!('encoding-indexes' in global)) + return; + encodings.forEach(function(category) { + if (category.heading !== 'Legacy single-byte encodings') + return; + category.encodings.forEach(function(encoding) { + var name = encoding.name; + var idx = index(name); + /** @param {{fatal: boolean}} options */ + decoders[name] = function(options) { + return new SingleByteDecoder(idx, options); + }; + /** @param {{fatal: boolean}} options */ + encoders[name] = function(options) { + return new SingleByteEncoder(idx, options); + }; + }); + }); + }()); + + // + // 10. Legacy multi-byte Chinese (simplified) encodings + // + + // 10.1 gbk + + // 10.1.1 gbk decoder + // gbk's decoder is gb18030's decoder. + /** @param {{fatal: boolean}} options */ + decoders['gbk'] = function(options) { + return new GB18030Decoder(options); + }; + + // 10.1.2 gbk encoder + // gbk's encoder is gb18030's encoder with its gbk flag set. + /** @param {{fatal: boolean}} options */ + encoders['gbk'] = function(options) { + return new GB18030Encoder(options, true); + }; + + // 10.2 gb18030 + + // 10.2.1 gb18030 decoder + /** + * @constructor + * @implements {Decoder} + * @param {{fatal: boolean}} options + */ + function GB18030Decoder(options) { + var fatal = options.fatal; + // gb18030's decoder has an associated gb18030 first, gb18030 + // second, and gb18030 third (all initially 0x00). + var /** @type {number} */ gb18030_first = 0x00, + /** @type {number} */ gb18030_second = 0x00, + /** @type {number} */ gb18030_third = 0x00; + /** + * @param {Stream} stream The stream of bytes being decoded. + * @param {number} bite The next byte read from the stream. + * @return {?(number|!Array.)} The next code point(s) + * decoded, or null if not enough data exists in the input + * stream to decode a complete code point. + */ + this.handler = function(stream, bite) { + // 1. If byte is end-of-stream and gb18030 first, gb18030 + // second, and gb18030 third are 0x00, return finished. + if (bite === end_of_stream && gb18030_first === 0x00 && + gb18030_second === 0x00 && gb18030_third === 0x00) { + return finished; + } + // 2. If byte is end-of-stream, and gb18030 first, gb18030 + // second, or gb18030 third is not 0x00, set gb18030 first, + // gb18030 second, and gb18030 third to 0x00, and return error. + if (bite === end_of_stream && + (gb18030_first !== 0x00 || gb18030_second !== 0x00 || gb18030_third !== 0x00)) { + gb18030_first = 0x00; + gb18030_second = 0x00; + gb18030_third = 0x00; + decoderError(fatal); + } + var code_point; + // 3. If gb18030 third is not 0x00, run these substeps: + if (gb18030_third !== 0x00) { + // 1. Let code point be null. + code_point = null; + // 2. If byte is in the range 0x30 to 0x39, set code point to + // the index gb18030 ranges code point for (((gb18030 first − + // 0x81) × 10 + gb18030 second − 0x30) × 126 + gb18030 third − + // 0x81) × 10 + byte − 0x30. + if (inRange(bite, 0x30, 0x39)) { + code_point = indexGB18030RangesCodePointFor( + (((gb18030_first - 0x81) * 10 + (gb18030_second - 0x30)) * 126 + + (gb18030_third - 0x81)) * 10 + bite - 0x30); + } + + // 3. Let buffer be a byte sequence consisting of gb18030 + // second, gb18030 third, and byte, in order. + var buffer = [gb18030_second, gb18030_third, bite]; + + // 4. Set gb18030 first, gb18030 second, and gb18030 third to + // 0x00. + gb18030_first = 0x00; + gb18030_second = 0x00; + gb18030_third = 0x00; + + // 5. If code point is null, prepend buffer to stream and + // return error. + if (code_point === null) { + stream.prepend(buffer); + return decoderError(fatal); + } + + // 6. Return a code point whose value is code point. + return code_point; + } + + // 4. If gb18030 second is not 0x00, run these substeps: + if (gb18030_second !== 0x00) { + + // 1. If byte is in the range 0x81 to 0xFE, set gb18030 third + // to byte and return continue. + if (inRange(bite, 0x81, 0xFE)) { + gb18030_third = bite; + return null; + } + + // 2. Prepend gb18030 second followed by byte to stream, set + // gb18030 first and gb18030 second to 0x00, and return error. + stream.prepend([gb18030_second, bite]); + gb18030_first = 0x00; + gb18030_second = 0x00; + return decoderError(fatal); + } + + // 5. If gb18030 first is not 0x00, run these substeps: + if (gb18030_first !== 0x00) { + + // 1. If byte is in the range 0x30 to 0x39, set gb18030 second + // to byte and return continue. + if (inRange(bite, 0x30, 0x39)) { + gb18030_second = bite; + return null; + } + + // 2. Let lead be gb18030 first, let pointer be null, and set + // gb18030 first to 0x00. + var lead = gb18030_first; + var pointer = null; + gb18030_first = 0x00; + + // 3. Let offset be 0x40 if byte is less than 0x7F and 0x41 + // otherwise. + var offset = bite < 0x7F ? 0x40 : 0x41; + + // 4. If byte is in the range 0x40 to 0x7E or 0x80 to 0xFE, + // set pointer to (lead − 0x81) × 190 + (byte − offset). + if (inRange(bite, 0x40, 0x7E) || inRange(bite, 0x80, 0xFE)) + pointer = (lead - 0x81) * 190 + (bite - offset); + + // 5. Let code point be null if pointer is null and the index + // code point for pointer in index gb18030 otherwise. + code_point = pointer === null ? null : + indexCodePointFor(pointer, index('gb18030')); + + // 6. If code point is null and byte is in the range 0x00 to + // 0x7F, prepend byte to stream. + if (code_point === null && inRange(bite, 0x00, 0x7F)) + stream.prepend(bite); + + // 7. If code point is null, return error. + if (code_point === null) + return decoderError(fatal); + + // 8. Return a code point whose value is code point. + return code_point; + } + + // 6. If byte is in the range 0x00 to 0x7F, return a code point + // whose value is byte. + if (inRange(bite, 0x00, 0x7F)) + return bite; + + // 7. If byte is 0x80, return code point U+20AC. + if (bite === 0x80) + return 0x20AC; + + // 8. If byte is in the range 0x81 to 0xFE, set gb18030 first to + // byte and return continue. + if (inRange(bite, 0x81, 0xFE)) { + gb18030_first = bite; + return null; + } + + // 9. Return error. + return decoderError(fatal); + }; + } + + // 10.2.2 gb18030 encoder + /** + * @constructor + * @implements {Encoder} + * @param {{fatal: boolean}} options + * @param {boolean=} gbk_flag + */ + function GB18030Encoder(options, gbk_flag) { + var fatal = options.fatal; + // gb18030's decoder has an associated gbk flag (initially unset). + /** + * @param {Stream} stream Input stream. + * @param {number} code_point Next code point read from the stream. + * @return {(number|!Array.)} Byte(s) to emit. + */ + this.handler = function(stream, code_point) { + // 1. If code point is end-of-stream, return finished. + if (code_point === end_of_stream) + return finished; + + // 2. If code point is in the range U+0000 to U+007F, return a + // byte whose value is code point. + if (inRange(code_point, 0x0000, 0x007F)) { + return code_point; + } + + // 3. If the gbk flag is set and code point is U+20AC, return + // byte 0x80. + if (gbk_flag && code_point === 0x20AC) + return 0x80; + + // 4. Let pointer be the index pointer for code point in index + // gb18030. + var pointer = indexPointerFor(code_point, index('gb18030')); + + // 5. If pointer is not null, run these substeps: + if (pointer !== null) { + + // 1. Let lead be pointer / 190 + 0x81. + var lead = div(pointer, 190) + 0x81; + + // 2. Let trail be pointer % 190. + var trail = pointer % 190; + + // 3. Let offset be 0x40 if trail is less than 0x3F and 0x41 otherwise. + var offset = trail < 0x3F ? 0x40 : 0x41; + + // 4. Return two bytes whose values are lead and trail + offset. + return [lead, trail + offset]; + } + + // 6. If gbk flag is set, return error with code point. + if (gbk_flag) + return encoderError(code_point); + + // 7. Set pointer to the index gb18030 ranges pointer for code + // point. + pointer = indexGB18030RangesPointerFor(code_point); + + // 8. Let byte1 be pointer / 10 / 126 / 10. + var byte1 = div(div(div(pointer, 10), 126), 10); + + // 9. Set pointer to pointer − byte1 × 10 × 126 × 10. + pointer = pointer - byte1 * 10 * 126 * 10; + + // 10. Let byte2 be pointer / 10 / 126. + var byte2 = div(div(pointer, 10), 126); + + // 11. Set pointer to pointer − byte2 × 10 × 126. + pointer = pointer - byte2 * 10 * 126; + + // 12. Let byte3 be pointer / 10. + var byte3 = div(pointer, 10); + + // 13. Let byte4 be pointer − byte3 × 10. + var byte4 = pointer - byte3 * 10; + + // 14. Return four bytes whose values are byte1 + 0x81, byte2 + + // 0x30, byte3 + 0x81, byte4 + 0x30. + return [byte1 + 0x81, + byte2 + 0x30, + byte3 + 0x81, + byte4 + 0x30]; + }; + } + + /** @param {{fatal: boolean}} options */ + encoders['gb18030'] = function(options) { + return new GB18030Encoder(options); + }; + /** @param {{fatal: boolean}} options */ + decoders['gb18030'] = function(options) { + return new GB18030Decoder(options); + }; + + + // + // 11. Legacy multi-byte Chinese (traditional) encodings + // + + // 11.1 big5 + + /** + * @constructor + * @implements {Decoder} + * @param {{fatal: boolean}} options + */ + function Big5Decoder(options) { + var fatal = options.fatal; + // big5's decoder has an associated big5 lead (initially 0x00). + var /** @type {number} */ big5_lead = 0x00; + + /** + * @param {Stream} stream The stream of bytes being decoded. + * @param {number} bite The next byte read from the stream. + * @return {?(number|!Array.)} The next code point(s) + * decoded, or null if not enough data exists in the input + * stream to decode a complete code point. + */ + this.handler = function(stream, bite) { + // 1. If byte is end-of-stream and big5 lead is not 0x00, set + // big5 lead to 0x00 and return error. + if (bite === end_of_stream && big5_lead !== 0x00) { + big5_lead = 0x00; + return decoderError(fatal); + } + + // 2. If byte is end-of-stream and big5 lead is 0x00, return + // finished. + if (bite === end_of_stream && big5_lead === 0x00) + return finished; + + // 3. If big5 lead is not 0x00, let lead be big5 lead, let + // pointer be null, set big5 lead to 0x00, and then run these + // substeps: + if (big5_lead !== 0x00) { + var lead = big5_lead; + var pointer = null; + big5_lead = 0x00; + + // 1. Let offset be 0x40 if byte is less than 0x7F and 0x62 + // otherwise. + var offset = bite < 0x7F ? 0x40 : 0x62; + + // 2. If byte is in the range 0x40 to 0x7E or 0xA1 to 0xFE, + // set pointer to (lead − 0x81) × 157 + (byte − offset). + if (inRange(bite, 0x40, 0x7E) || inRange(bite, 0xA1, 0xFE)) + pointer = (lead - 0x81) * 157 + (bite - offset); + + // 3. If there is a row in the table below whose first column + // is pointer, return the two code points listed in its second + // column + // Pointer | Code points + // --------+-------------- + // 1133 | U+00CA U+0304 + // 1135 | U+00CA U+030C + // 1164 | U+00EA U+0304 + // 1166 | U+00EA U+030C + switch (pointer) { + case 1133: return [0x00CA, 0x0304]; + case 1135: return [0x00CA, 0x030C]; + case 1164: return [0x00EA, 0x0304]; + case 1166: return [0x00EA, 0x030C]; + } + + // 4. Let code point be null if pointer is null and the index + // code point for pointer in index big5 otherwise. + var code_point = (pointer === null) ? null : + indexCodePointFor(pointer, index('big5')); + + // 5. If code point is null and byte is in the range 0x00 to + // 0x7F, prepend byte to stream. + if (code_point === null && inRange(bite, 0x00, 0x7F)) + stream.prepend(bite); + + // 6. If code point is null, return error. + if (code_point === null) + return decoderError(fatal); + + // 7. Return a code point whose value is code point. + return code_point; + } + + // 4. If byte is in the range 0x00 to 0x7F, return a code point + // whose value is byte. + if (inRange(bite, 0x00, 0x7F)) + return bite; + + // 5. If byte is in the range 0x81 to 0xFE, set big5 lead to + // byte and return continue. + if (inRange(bite, 0x81, 0xFE)) { + big5_lead = bite; + return null; + } + + // 6. Return error. + return decoderError(fatal); + }; + } + + /** + * @constructor + * @implements {Encoder} + * @param {{fatal: boolean}} options + */ + function Big5Encoder(options) { + var fatal = options.fatal; + /** + * @param {Stream} stream Input stream. + * @param {number} code_point Next code point read from the stream. + * @return {(number|!Array.)} Byte(s) to emit. + */ + this.handler = function(stream, code_point) { + // 1. If code point is end-of-stream, return finished. + if (code_point === end_of_stream) + return finished; + + // 2. If code point is in the range U+0000 to U+007F, return a + // byte whose value is code point. + if (inRange(code_point, 0x0000, 0x007F)) + return code_point; + + // 3. Let pointer be the index big5 pointer for code point. + var pointer = indexBig5PointerFor(code_point, index('big5')); + + // 4. If pointer is null, return error with code point. + if (pointer === null) + return encoderError(code_point); + + // 5. Let lead be pointer / 157 + 0x81. + var lead = div(pointer, 157) + 0x81; + + // 6. If lead is less than 0xA1, return error with code point. + if (lead < 0xA1) + return encoderError(code_point); + + // 7. Let trail be pointer % 157. + var trail = pointer % 157; + + // 8. Let offset be 0x40 if trail is less than 0x3F and 0x62 + // otherwise. + var offset = trail < 0x3F ? 0x40 : 0x62; + + // Return two bytes whose values are lead and trail + offset. + return [lead, trail + offset]; + }; + } + + /** @param {{fatal: boolean}} options */ + encoders['big5'] = function(options) { + return new Big5Encoder(options); + }; + /** @param {{fatal: boolean}} options */ + decoders['big5'] = function(options) { + return new Big5Decoder(options); + }; + + + // + // 12. Legacy multi-byte Japanese encodings + // + + // 12.1 euc-jp + + /** + * @constructor + * @implements {Decoder} + * @param {{fatal: boolean}} options + */ + function EUCJPDecoder(options) { + var fatal = options.fatal; + + // euc-jp's decoder has an associated euc-jp jis0212 flag + // (initially unset) and euc-jp lead (initially 0x00). + var /** @type {boolean} */ eucjp_jis0212_flag = false, + /** @type {number} */ eucjp_lead = 0x00; + + /** + * @param {Stream} stream The stream of bytes being decoded. + * @param {number} bite The next byte read from the stream. + * @return {?(number|!Array.)} The next code point(s) + * decoded, or null if not enough data exists in the input + * stream to decode a complete code point. + */ + this.handler = function(stream, bite) { + // 1. If byte is end-of-stream and euc-jp lead is not 0x00, set + // euc-jp lead to 0x00, and return error. + if (bite === end_of_stream && eucjp_lead !== 0x00) { + eucjp_lead = 0x00; + return decoderError(fatal); + } + + // 2. If byte is end-of-stream and euc-jp lead is 0x00, return + // finished. + if (bite === end_of_stream && eucjp_lead === 0x00) + return finished; + + // 3. If euc-jp lead is 0x8E and byte is in the range 0xA1 to + // 0xDF, set euc-jp lead to 0x00 and return a code point whose + // value is 0xFF61 + byte − 0xA1. + if (eucjp_lead === 0x8E && inRange(bite, 0xA1, 0xDF)) { + eucjp_lead = 0x00; + return 0xFF61 + bite - 0xA1; + } + + // 4. If euc-jp lead is 0x8F and byte is in the range 0xA1 to + // 0xFE, set the euc-jp jis0212 flag, set euc-jp lead to byte, + // and return continue. + if (eucjp_lead === 0x8F && inRange(bite, 0xA1, 0xFE)) { + eucjp_jis0212_flag = true; + eucjp_lead = bite; + return null; + } + + // 5. If euc-jp lead is not 0x00, let lead be euc-jp lead, set + // euc-jp lead to 0x00, and run these substeps: + if (eucjp_lead !== 0x00) { + var lead = eucjp_lead; + eucjp_lead = 0x00; + + // 1. Let code point be null. + var code_point = null; + + // 2. If lead and byte are both in the range 0xA1 to 0xFE, set + // code point to the index code point for (lead − 0xA1) × 94 + + // byte − 0xA1 in index jis0208 if the euc-jp jis0212 flag is + // unset and in index jis0212 otherwise. + if (inRange(lead, 0xA1, 0xFE) && inRange(bite, 0xA1, 0xFE)) { + code_point = indexCodePointFor( + (lead - 0xA1) * 94 + (bite - 0xA1), + index(!eucjp_jis0212_flag ? 'jis0208' : 'jis0212')); + } + + // 3. Unset the euc-jp jis0212 flag. + eucjp_jis0212_flag = false; + + // 4. If byte is not in the range 0xA1 to 0xFE, prepend byte + // to stream. + if (!inRange(bite, 0xA1, 0xFE)) + stream.prepend(bite); + + // 5. If code point is null, return error. + if (code_point === null) + return decoderError(fatal); + + // 6. Return a code point whose value is code point. + return code_point; + } + + // 6. If byte is in the range 0x00 to 0x7F, return a code point + // whose value is byte. + if (inRange(bite, 0x00, 0x7F)) + return bite; + + // 7. If byte is 0x8E, 0x8F, or in the range 0xA1 to 0xFE, set + // euc-jp lead to byte and return continue. + if (bite === 0x8E || bite === 0x8F || inRange(bite, 0xA1, 0xFE)) { + eucjp_lead = bite; + return null; + } + + // 8. Return error. + return decoderError(fatal); + }; + } + + /** + * @constructor + * @implements {Encoder} + * @param {{fatal: boolean}} options + */ + function EUCJPEncoder(options) { + var fatal = options.fatal; + /** + * @param {Stream} stream Input stream. + * @param {number} code_point Next code point read from the stream. + * @return {(number|!Array.)} Byte(s) to emit. + */ + this.handler = function(stream, code_point) { + // 1. If code point is end-of-stream, return finished. + if (code_point === end_of_stream) + return finished; + + // 2. If code point is in the range U+0000 to U+007F, return a + // byte whose value is code point. + if (inRange(code_point, 0x0000, 0x007F)) + return code_point; + + // 3. If code point is U+00A5, return byte 0x5C. + if (code_point === 0x00A5) + return 0x5C; + + // 4. If code point is U+203E, return byte 0x7E. + if (code_point === 0x203E) + return 0x7E; + + // 5. If code point is in the range U+FF61 to U+FF9F, return two + // bytes whose values are 0x8E and code point − 0xFF61 + 0xA1. + if (inRange(code_point, 0xFF61, 0xFF9F)) + return [0x8E, code_point - 0xFF61 + 0xA1]; + + // 6. If code point is U+2022, set it to U+FF0D. + if (code_point === 0x2022) + code_point = 0xFF0D; + + // 7. Let pointer be the index pointer for code point in index + // jis0208. + var pointer = indexPointerFor(code_point, index('jis0208')); + + // 8. If pointer is null, return error with code point. + if (pointer === null) + return encoderError(code_point); + + // 9. Let lead be pointer / 94 + 0xA1. + var lead = div(pointer, 94) + 0xA1; + + // 10. Let trail be pointer % 94 + 0xA1. + var trail = pointer % 94 + 0xA1; + + // 11. Return two bytes whose values are lead and trail. + return [lead, trail]; + }; + } + + /** @param {{fatal: boolean}} options */ + encoders['euc-jp'] = function(options) { + return new EUCJPEncoder(options); + }; + /** @param {{fatal: boolean}} options */ + decoders['euc-jp'] = function(options) { + return new EUCJPDecoder(options); + }; + + // 12.2 iso-2022-jp + + /** + * @constructor + * @implements {Decoder} + * @param {{fatal: boolean}} options + */ + function ISO2022JPDecoder(options) { + var fatal = options.fatal; + /** @enum */ + var states = { + ASCII: 0, + Roman: 1, + Katakana: 2, + LeadByte: 3, + TrailByte: 4, + EscapeStart: 5, + Escape: 6 + }; + // iso-2022-jp's decoder has an associated iso-2022-jp decoder + // state (initially ASCII), iso-2022-jp decoder output state + // (initially ASCII), iso-2022-jp lead (initially 0x00), and + // iso-2022-jp output flag (initially unset). + var /** @type {number} */ iso2022jp_decoder_state = states.ASCII, + /** @type {number} */ iso2022jp_decoder_output_state = states.ASCII, + /** @type {number} */ iso2022jp_lead = 0x00, + /** @type {boolean} */ iso2022jp_output_flag = false; + /** + * @param {Stream} stream The stream of bytes being decoded. + * @param {number} bite The next byte read from the stream. + * @return {?(number|!Array.)} The next code point(s) + * decoded, or null if not enough data exists in the input + * stream to decode a complete code point. + */ + this.handler = function(stream, bite) { + // switching on iso-2022-jp decoder state: + switch (iso2022jp_decoder_state) { + default: + case states.ASCII: + // ASCII + // Based on byte: + + // 0x1B + if (bite === 0x1B) { + // Set iso-2022-jp decoder state to escape start and return + // continue. + iso2022jp_decoder_state = states.EscapeStart; + return null; + } + + // 0x00 to 0x7F, excluding 0x0E, 0x0F, and 0x1B + if (inRange(bite, 0x00, 0x7F) && bite !== 0x0E + && bite !== 0x0F && bite !== 0x1B) { + // Unset the iso-2022-jp output flag and return a code point + // whose value is byte. + iso2022jp_output_flag = false; + return bite; + } + + // end-of-stream + if (bite === end_of_stream) { + // Return finished. + return finished; + } + + // Otherwise + // Unset the iso-2022-jp output flag and return error. + iso2022jp_output_flag = false; + return decoderError(fatal); + + case states.Roman: + // Roman + // Based on byte: + + // 0x1B + if (bite === 0x1B) { + // Set iso-2022-jp decoder state to escape start and return + // continue. + iso2022jp_decoder_state = states.EscapeStart; + return null; + } + + // 0x5C + if (bite === 0x5C) { + // Unset the iso-2022-jp output flag and return code point + // U+00A5. + iso2022jp_output_flag = false; + return 0x00A5; + } + + // 0x7E + if (bite === 0x7E) { + // Unset the iso-2022-jp output flag and return code point + // U+203E. + iso2022jp_output_flag = false; + return 0x203E; + } + + // 0x00 to 0x7F, excluding 0x0E, 0x0F, 0x1B, 0x5C, and 0x7E + if (inRange(bite, 0x00, 0x7F) && bite !== 0x0E && bite !== 0x0F + && bite !== 0x1B && bite !== 0x5C && bite !== 0x7E) { + // Unset the iso-2022-jp output flag and return a code point + // whose value is byte. + iso2022jp_output_flag = false; + return bite; + } + + // end-of-stream + if (bite === end_of_stream) { + // Return finished. + return finished; + } + + // Otherwise + // Unset the iso-2022-jp output flag and return error. + iso2022jp_output_flag = false; + return decoderError(fatal); + + case states.Katakana: + // Katakana + // Based on byte: + + // 0x1B + if (bite === 0x1B) { + // Set iso-2022-jp decoder state to escape start and return + // continue. + iso2022jp_decoder_state = states.EscapeStart; + return null; + } + + // 0x21 to 0x5F + if (inRange(bite, 0x21, 0x5F)) { + // Unset the iso-2022-jp output flag and return a code point + // whose value is 0xFF61 + byte − 0x21. + iso2022jp_output_flag = false; + return 0xFF61 + bite - 0x21; + } + + // end-of-stream + if (bite === end_of_stream) { + // Return finished. + return finished; + } + + // Otherwise + // Unset the iso-2022-jp output flag and return error. + iso2022jp_output_flag = false; + return decoderError(fatal); + + case states.LeadByte: + // Lead byte + // Based on byte: + + // 0x1B + if (bite === 0x1B) { + // Set iso-2022-jp decoder state to escape start and return + // continue. + iso2022jp_decoder_state = states.EscapeStart; + return null; + } + + // 0x21 to 0x7E + if (inRange(bite, 0x21, 0x7E)) { + // Unset the iso-2022-jp output flag, set iso-2022-jp lead + // to byte, iso-2022-jp decoder state to trail byte, and + // return continue. + iso2022jp_output_flag = false; + iso2022jp_lead = bite; + iso2022jp_decoder_state = states.TrailByte; + return null; + } + + // end-of-stream + if (bite === end_of_stream) { + // Return finished. + return finished; + } + + // Otherwise + // Unset the iso-2022-jp output flag and return error. + iso2022jp_output_flag = false; + return decoderError(fatal); + + case states.TrailByte: + // Trail byte + // Based on byte: + + // 0x1B + if (bite === 0x1B) { + // Set iso-2022-jp decoder state to escape start and return + // continue. + iso2022jp_decoder_state = states.EscapeStart; + return decoderError(fatal); + } + + // 0x21 to 0x7E + if (inRange(bite, 0x21, 0x7E)) { + // 1. Set the iso-2022-jp decoder state to lead byte. + iso2022jp_decoder_state = states.LeadByte; + + // 2. Let pointer be (iso-2022-jp lead − 0x21) × 94 + byte − 0x21. + var pointer = (iso2022jp_lead - 0x21) * 94 + bite - 0x21; + + // 3. Let code point be the index code point for pointer in index jis0208. + var code_point = indexCodePointFor(pointer, index('jis0208')); + + // 4. If code point is null, return error. + if (code_point === null) + return decoderError(fatal); + + // 5. Return a code point whose value is code point. + return code_point; + } + + // end-of-stream + if (bite === end_of_stream) { + // Set the iso-2022-jp decoder state to lead byte, prepend + // byte to stream, and return error. + iso2022jp_decoder_state = states.LeadByte; + stream.prepend(bite); + return decoderError(fatal); + } + + // Otherwise + // Set iso-2022-jp decoder state to lead byte and return + // error. + iso2022jp_decoder_state = states.LeadByte; + return decoderError(fatal); + + case states.EscapeStart: + // Escape start + + // 1. If byte is either 0x24 or 0x28, set iso-2022-jp lead to + // byte, iso-2022-jp decoder state to escape, and return + // continue. + if (bite === 0x24 || bite === 0x28) { + iso2022jp_lead = bite; + iso2022jp_decoder_state = states.Escape; + return null; + } + + // 2. Prepend byte to stream. + stream.prepend(bite); + + // 3. Unset the iso-2022-jp output flag, set iso-2022-jp + // decoder state to iso-2022-jp decoder output state, and + // return error. + iso2022jp_output_flag = false; + iso2022jp_decoder_state = iso2022jp_decoder_output_state; + return decoderError(fatal); + + case states.Escape: + // Escape + + // 1. Let lead be iso-2022-jp lead and set iso-2022-jp lead to + // 0x00. + var lead = iso2022jp_lead; + iso2022jp_lead = 0x00; + + // 2. Let state be null. + var state = null; + + // 3. If lead is 0x28 and byte is 0x42, set state to ASCII. + if (lead === 0x28 && bite === 0x42) + state = states.ASCII; + + // 4. If lead is 0x28 and byte is 0x4A, set state to Roman. + if (lead === 0x28 && bite === 0x4A) + state = states.Roman; + + // 5. If lead is 0x28 and byte is 0x49, set state to Katakana. + if (lead === 0x28 && bite === 0x49) + state = states.Katakana; + + // 6. If lead is 0x24 and byte is either 0x40 or 0x42, set + // state to lead byte. + if (lead === 0x24 && (bite === 0x40 || bite === 0x42)) + state = states.LeadByte; + + // 7. If state is non-null, run these substeps: + if (state !== null) { + // 1. Set iso-2022-jp decoder state and iso-2022-jp decoder + // output state to states. + iso2022jp_decoder_state = iso2022jp_decoder_state = state; + + // 2. Let output flag be the iso-2022-jp output flag. + var output_flag = iso2022jp_output_flag; + + // 3. Set the iso-2022-jp output flag. + iso2022jp_output_flag = true; + + // 4. Return continue, if output flag is unset, and error + // otherwise. + return !output_flag ? null : decoderError(fatal); + } + + // 8. Prepend lead and byte to stream. + stream.prepend([lead, bite]); + + // 9. Unset the iso-2022-jp output flag, set iso-2022-jp + // decoder state to iso-2022-jp decoder output state and + // return error. + iso2022jp_output_flag = false; + iso2022jp_decoder_state = iso2022jp_decoder_output_state; + return decoderError(fatal); + } + }; + } + + /** + * @constructor + * @implements {Encoder} + * @param {{fatal: boolean}} options + */ + function ISO2022JPEncoder(options) { + var fatal = options.fatal; + // iso-2022-jp's encoder has an associated iso-2022-jp encoder + // state which is one of ASCII, Roman, and jis0208 (initially + // ASCII). + /** @enum */ + var states = { + ASCII: 0, + Roman: 1, + jis0208: 2 + }; + var /** @type {number} */ iso2022jp_state = states.ASCII; + /** + * @param {Stream} stream Input stream. + * @param {number} code_point Next code point read from the stream. + * @return {(number|!Array.)} Byte(s) to emit. + */ + this.handler = function(stream, code_point) { + // 1. If code point is end-of-stream and iso-2022-jp encoder + // state is not ASCII, prepend code point to stream, set + // iso-2022-jp encoder state to ASCII, and return three bytes + // 0x1B 0x28 0x42. + if (code_point === end_of_stream && + iso2022jp_state !== states.ASCII) { + stream.prepend(code_point); + return [0x1B, 0x28, 0x42]; + } + + // 2. If code point is end-of-stream and iso-2022-jp encoder + // state is ASCII, return finished. + if (code_point === end_of_stream && iso2022jp_state === states.ASCII) + return finished; + + // 3. If iso-2022-jp encoder state is ASCII and code point is in + // the range U+0000 to U+007F, return a byte whose value is code + // point. + if (iso2022jp_state === states.ASCII && + inRange(code_point, 0x0000, 0x007F)) + return code_point; + + // 4. If iso-2022-jp encoder state is Roman and code point is in + // the range U+0000 to U+007F, excluding U+005C and U+007E, or + // is U+00A5 or U+203E, run these substeps: + if (iso2022jp_state === states.Roman && + inRange(code_point, 0x0000, 0x007F) && + code_point !== 0x005C && code_point !== 0x007E) { + + // 1. If code point is in the range U+0000 to U+007F, return a + // byte whose value is code point. + if (inRange(code_point, 0x0000, 0x007F)) + return code_point; + + // 2. If code point is U+00A5, return byte 0x5C. + if (code_point === 0x00A5) + return 0x5C; + + // 3. If code point is U+203E, return byte 0x7E. + if (code_point === 0x203E) + return 0x7E; + } + + // 5. If code point is in the range U+0000 to U+007F, and + // iso-2022-jp encoder state is not ASCII, prepend code point to + // stream, set iso-2022-jp encoder state to ASCII, and return + // three bytes 0x1B 0x28 0x42. + if (inRange(code_point, 0x0000, 0x007F) && + iso2022jp_state !== states.ASCII) { + stream.prepend(code_point); + iso2022jp_state = states.ASCII; + return [0x1B, 0x28, 0x42]; + } + + // 6. If code point is either U+00A5 or U+203E, and iso-2022-jp + // encoder state is not Roman, prepend code point to stream, set + // iso-2022-jp encoder state to Roman, and return three bytes + // 0x1B 0x28 0x4A. + if ((code_point === 0x00A5 || code_point === 0x203E) && + iso2022jp_state !== states.Roman) { + stream.prepend(code_point); + iso2022jp_state = states.Roman; + return [0x1B, 0x28, 0x4A]; + } + + // 7. If code point is U+2022, set it to U+FF0D. + if (code_point === 0x2022) + code_point = 0xFF0D; + + // 8. Let pointer be the index pointer for code point in index + // jis0208. + var pointer = indexPointerFor(code_point, index('jis0208')); + + // 9. If pointer is null, return error with code point. + if (pointer === null) + return encoderError(code_point); + + // 10. If iso-2022-jp encoder state is not jis0208, prepend code + // point to stream, set iso-2022-jp encoder state to jis0208, + // and return three bytes 0x1B 0x24 0x42. + if (iso2022jp_state !== states.jis0208) { + stream.prepend(code_point); + iso2022jp_state = states.jis0208; + return [0x1B, 0x24, 0x42]; + } + + // 11. Let lead be pointer / 94 + 0x21. + var lead = div(pointer, 94) + 0x21; + + // 12. Let trail be pointer % 94 + 0x21. + var trail = pointer % 94 + 0x21; + + // 13. Return two bytes whose values are lead and trail. + return [lead, trail]; + }; + } + + /** @param {{fatal: boolean}} options */ + encoders['iso-2022-jp'] = function(options) { + return new ISO2022JPEncoder(options); + }; + /** @param {{fatal: boolean}} options */ + decoders['iso-2022-jp'] = function(options) { + return new ISO2022JPDecoder(options); + }; + + // 12.3 shift_jis + + /** + * @constructor + * @implements {Decoder} + * @param {{fatal: boolean}} options + */ + function ShiftJISDecoder(options) { + var fatal = options.fatal; + // shift_jis's decoder has an associated shift_jis lead (initially + // 0x00). + var /** @type {number} */ shiftjis_lead = 0x00; + /** + * @param {Stream} stream The stream of bytes being decoded. + * @param {number} bite The next byte read from the stream. + * @return {?(number|!Array.)} The next code point(s) + * decoded, or null if not enough data exists in the input + * stream to decode a complete code point. + */ + this.handler = function(stream, bite) { + // 1. If byte is end-of-stream and shift_jis lead is not 0x00, + // set shift_jis lead to 0x00 and return error. + if (bite === end_of_stream && shiftjis_lead !== 0x00) { + shiftjis_lead = 0x00; + return decoderError(fatal); + } + + // 2. If byte is end-of-stream and shift_jis lead is 0x00, + // return finished. + if (bite === end_of_stream && shiftjis_lead === 0x00) + return finished; + + // 3. If shift_jis lead is not 0x00, let lead be shift_jis lead, + // let pointer be null, set shift_jis lead to 0x00, and then run + // these substeps: + if (shiftjis_lead !== 0x00) { + var lead = shiftjis_lead; + var pointer = null; + shiftjis_lead = 0x00; + + // 1. Let offset be 0x40, if byte is less than 0x7F, and 0x41 + // otherwise. + var offset = (bite < 0x7F) ? 0x40 : 0x41; + + // 2. Let lead offset be 0x81, if lead is less than 0xA0, and + // 0xC1 otherwise. + var lead_offset = (lead < 0xA0) ? 0x81 : 0xC1; + + // 3. If byte is in the range 0x40 to 0x7E or 0x80 to 0xFC, + // set pointer to (lead − lead offset) × 188 + byte − offset. + if (inRange(bite, 0x40, 0x7E) || inRange(bite, 0x80, 0xFC)) + pointer = (lead - lead_offset) * 188 + bite - offset; + + // 4. Let code point be null, if pointer is null, and the + // index code point for pointer in index jis0208 otherwise. + var code_point = (pointer === null) ? null : + indexCodePointFor(pointer, index('jis0208')); + + // 5. If code point is null and pointer is in the range 8836 + // to 10528, return a code point whose value is 0xE000 + + // pointer − 8836. + if (code_point === null && pointer !== null && + inRange(pointer, 8836, 10528)) + return 0xE000 + pointer - 8836; + + // 6. If code point is null and byte is in the range 0x00 to + // 0x7F, prepend byte to stream. + if (code_point === null && inRange(bite, 0x00, 0x7F)) + stream.prepend(bite); + + // 7. If code point is null, return error. + if (code_point === null) + return decoderError(fatal); + + // 8. Return a code point whose value is code point. + return code_point; + } + + // 4. If byte is in the range 0x00 to 0x80, return a code point + // whose value is byte. + if (inRange(bite, 0x00, 0x80)) + return bite; + + // 5. If byte is in the range 0xA1 to 0xDF, return a code point + // whose value is 0xFF61 + byte − 0xA1. + if (inRange(bite, 0xA1, 0xDF)) + return 0xFF61 + bite - 0xA1; + + // 6. If byte is in the range 0x81 to 0x9F or 0xE0 to 0xFC, set + // shift_jis lead to byte and return continue. + if (inRange(bite, 0x81, 0x9F) || inRange(bite, 0xE0, 0xFC)) { + shiftjis_lead = bite; + return null; + } + + // 7. Return error. + return decoderError(fatal); + }; + } + + /** + * @constructor + * @implements {Encoder} + * @param {{fatal: boolean}} options + */ + function ShiftJISEncoder(options) { + var fatal = options.fatal; + /** + * @param {Stream} stream Input stream. + * @param {number} code_point Next code point read from the stream. + * @return {(number|!Array.)} Byte(s) to emit. + */ + this.handler = function(stream, code_point) { + // 1. If code point is end-of-stream, return finished. + if (code_point === end_of_stream) + return finished; + + // 2. If code point is in the range U+0000 to U+0080, return a + // byte whose value is code point. + if (inRange(code_point, 0x0000, 0x0080)) + return code_point; + + // 3. If code point is U+00A5, return byte 0x5C. + if (code_point === 0x00A5) + return 0x5C; + + // 4. If code point is U+203E, return byte 0x7E. + if (code_point === 0x203E) + return 0x7E; + + // 5. If code point is in the range U+FF61 to U+FF9F, return a + // byte whose value is code point − 0xFF61 + 0xA1. + if (inRange(code_point, 0xFF61, 0xFF9F)) + return code_point - 0xFF61 + 0xA1; + + // 6. If code point is U+2022, set it to U+FF0D. + if (code_point === 0x2022) + code_point = 0xFF0D; + + // 7. Let pointer be the index shift_jis pointer for code point. + var pointer = indexShiftJISPointerFor(code_point); + + // 8. If pointer is null, return error with code point. + if (pointer === null) + return encoderError(code_point); + + // 9. Let lead be pointer / 188. + var lead = div(pointer, 188); + + // 10. Let lead offset be 0x81, if lead is less than 0x1F, and + // 0xC1 otherwise. + var lead_offset = (lead < 0x1F) ? 0x81 : 0xC1; + + // 11. Let trail be pointer % 188. + var trail = pointer % 188; + + // 12. Let offset be 0x40, if trail is less than 0x3F, and 0x41 + // otherwise. + var offset = (trail < 0x3F) ? 0x40 : 0x41; + + // 13. Return two bytes whose values are lead + lead offset and + // trail + offset. + return [lead + lead_offset, trail + offset]; + }; + } + + /** @param {{fatal: boolean}} options */ + encoders['shift_jis'] = function(options) { + return new ShiftJISEncoder(options); + }; + /** @param {{fatal: boolean}} options */ + decoders['shift_jis'] = function(options) { + return new ShiftJISDecoder(options); + }; + + // + // 13. Legacy multi-byte Korean encodings + // + + // 13.1 euc-kr + + /** + * @constructor + * @implements {Decoder} + * @param {{fatal: boolean}} options + */ + function EUCKRDecoder(options) { + var fatal = options.fatal; + + // euc-kr's decoder has an associated euc-kr lead (initially 0x00). + var /** @type {number} */ euckr_lead = 0x00; + /** + * @param {Stream} stream The stream of bytes being decoded. + * @param {number} bite The next byte read from the stream. + * @return {?(number|!Array.)} The next code point(s) + * decoded, or null if not enough data exists in the input + * stream to decode a complete code point. + */ + this.handler = function(stream, bite) { + // 1. If byte is end-of-stream and euc-kr lead is not 0x00, set + // euc-kr lead to 0x00 and return error. + if (bite === end_of_stream && euckr_lead !== 0) { + euckr_lead = 0x00; + return decoderError(fatal); + } + + // 2. If byte is end-of-stream and euc-kr lead is 0x00, return + // finished. + if (bite === end_of_stream && euckr_lead === 0) + return finished; + + // 3. If euc-kr lead is not 0x00, let lead be euc-kr lead, let + // pointer be null, set euc-kr lead to 0x00, and then run these + // substeps: + if (euckr_lead !== 0x00) { + var lead = euckr_lead; + var pointer = null; + euckr_lead = 0x00; + + // 1. If byte is in the range 0x41 to 0xFE, set pointer to + // (lead − 0x81) × 190 + (byte − 0x41). + if (inRange(bite, 0x41, 0xFE)) + pointer = (lead - 0x81) * 190 + (bite - 0x41); + + // 2. Let code point be null, if pointer is null, and the + // index code point for pointer in index euc-kr otherwise. + var code_point = (pointer === null) ? null : indexCodePointFor(pointer, index('euc-kr')); + + // 3. If code point is null and byte is in the range 0x00 to + // 0x7F, prepend byte to stream. + if (pointer === null && inRange(bite, 0x00, 0x7F)) + stream.prepend(bite); + + // 4. If code point is null, return error. + if (code_point === null) + return decoderError(fatal); + + // 5. Return a code point whose value is code point. + return code_point; + } + + // 4. If byte is in the range 0x00 to 0x7F, return a code point + // whose value is byte. + if (inRange(bite, 0x00, 0x7F)) + return bite; + + // 5. If byte is in the range 0x81 to 0xFE, set euc-kr lead to + // byte and return continue. + if (inRange(bite, 0x81, 0xFE)) { + euckr_lead = bite; + return null; + } + + // 6. Return error. + return decoderError(fatal); + }; + } + + /** + * @constructor + * @implements {Encoder} + * @param {{fatal: boolean}} options + */ + function EUCKREncoder(options) { + var fatal = options.fatal; + /** + * @param {Stream} stream Input stream. + * @param {number} code_point Next code point read from the stream. + * @return {(number|!Array.)} Byte(s) to emit. + */ + this.handler = function(stream, code_point) { + // 1. If code point is end-of-stream, return finished. + if (code_point === end_of_stream) + return finished; + + // 2. If code point is in the range U+0000 to U+007F, return a + // byte whose value is code point. + if (inRange(code_point, 0x0000, 0x007F)) + return code_point; + + // 3. Let pointer be the index pointer for code point in index + // euc-kr. + var pointer = indexPointerFor(code_point, index('euc-kr')); + + // 4. If pointer is null, return error with code point. + if (pointer === null) + return encoderError(code_point); + + // 5. Let lead be pointer / 190 + 0x81. + var lead = div(pointer, 190) + 0x81; + + // 6. Let trail be pointer % 190 + 0x41. + var trail = (pointer % 190) + 0x41; + + // 7. Return two bytes whose values are lead and trail. + return [lead, trail]; + }; + } + + /** @param {{fatal: boolean}} options */ + encoders['euc-kr'] = function(options) { + return new EUCKREncoder(options); + }; + /** @param {{fatal: boolean}} options */ + decoders['euc-kr'] = function(options) { + return new EUCKRDecoder(options); + }; + + + // + // 14. Legacy miscellaneous encodings + // + + // 14.1 replacement + + // Not needed - API throws RangeError + + // 14.2 utf-16 + + /** + * @param {number} code_unit + * @param {boolean} utf16be + * @return {!Array.} bytes + */ + function convertCodeUnitToBytes(code_unit, utf16be) { + // 1. Let byte1 be code unit >> 8. + var byte1 = code_unit >> 8; + + // 2. Let byte2 be code unit & 0x00FF. + var byte2 = code_unit & 0x00FF; + + // 3. Then return the bytes in order: + // utf-16be flag is set: byte1, then byte2. + if (utf16be) + return [byte1, byte2]; + // utf-16be flag is unset: byte2, then byte1. + return [byte2, byte1]; + } + + /** + * @constructor + * @implements {Decoder} + * @param {boolean} utf16_be True if big-endian, false if little-endian. + * @param {{fatal: boolean}} options + */ + function UTF16Decoder(utf16_be, options) { + var fatal = options.fatal; + var /** @type {?number} */ utf16_lead_byte = null, + /** @type {?number} */ utf16_lead_surrogate = null; + /** + * @param {Stream} stream The stream of bytes being decoded. + * @param {number} bite The next byte read from the stream. + * @return {?(number|!Array.)} The next code point(s) + * decoded, or null if not enough data exists in the input + * stream to decode a complete code point. + */ + this.handler = function(stream, bite) { + // 1. If byte is end-of-stream and either utf-16 lead byte or + // utf-16 lead surrogate is not null, set utf-16 lead byte and + // utf-16 lead surrogate to null, and return error. + if (bite === end_of_stream && (utf16_lead_byte !== null || + utf16_lead_surrogate !== null)) { + return decoderError(fatal); + } + + // 2. If byte is end-of-stream and utf-16 lead byte and utf-16 + // lead surrogate are null, return finished. + if (bite === end_of_stream && utf16_lead_byte === null && + utf16_lead_surrogate === null) { + return finished; + } + + // 3. If utf-16 lead byte is null, set utf-16 lead byte to byte + // and return continue. + if (utf16_lead_byte === null) { + utf16_lead_byte = bite; + return null; + } + + // 4. Let code unit be the result of: + var code_unit; + if (utf16_be) { + // utf-16be decoder flag is set + // (utf-16 lead byte << 8) + byte. + code_unit = (utf16_lead_byte << 8) + bite; + } else { + // utf-16be decoder flag is unset + // (byte << 8) + utf-16 lead byte. + code_unit = (bite << 8) + utf16_lead_byte; + } + // Then set utf-16 lead byte to null. + utf16_lead_byte = null; + + // 5. If utf-16 lead surrogate is not null, let lead surrogate + // be utf-16 lead surrogate, set utf-16 lead surrogate to null, + // and then run these substeps: + if (utf16_lead_surrogate !== null) { + var lead_surrogate = utf16_lead_surrogate; + utf16_lead_surrogate = null; + + // 1. If code unit is in the range U+DC00 to U+DFFF, return a + // code point whose value is 0x10000 + ((lead surrogate − + // 0xD800) << 10) + (code unit − 0xDC00). + if (inRange(code_unit, 0xDC00, 0xDFFF)) { + return 0x10000 + (lead_surrogate - 0xD800) * 0x400 + + (code_unit - 0xDC00); + } + + // 2. Prepend the sequence resulting of converting code unit + // to bytes using utf-16be decoder flag to stream and return + // error. + stream.prepend(convertCodeUnitToBytes(code_unit, utf16_be)); + return decoderError(fatal); + } + + // 6. If code unit is in the range U+D800 to U+DBFF, set utf-16 + // lead surrogate to code unit and return continue. + if (inRange(code_unit, 0xD800, 0xDBFF)) { + utf16_lead_surrogate = code_unit; + return null; + } + + // 7. If code unit is in the range U+DC00 to U+DFFF, return + // error. + if (inRange(code_unit, 0xDC00, 0xDFFF)) + return decoderError(fatal); + + // 8. Return code point code unit. + return code_unit; + }; + } + + /** + * @constructor + * @implements {Encoder} + * @param {boolean} utf16_be True if big-endian, false if little-endian. + * @param {{fatal: boolean}} options + */ + function UTF16Encoder(utf16_be, options) { + var fatal = options.fatal; + /** + * @param {Stream} stream Input stream. + * @param {number} code_point Next code point read from the stream. + * @return {(number|!Array.)} Byte(s) to emit. + */ + this.handler = function(stream, code_point) { + // 1. If code point is end-of-stream, return finished. + if (code_point === end_of_stream) + return finished; + + // 2. If code point is in the range U+0000 to U+FFFF, return the + // sequence resulting of converting code point to bytes using + // utf-16be encoder flag. + if (inRange(code_point, 0x0000, 0xFFFF)) + return convertCodeUnitToBytes(code_point, utf16_be); + + // 3. Let lead be ((code point − 0x10000) >> 10) + 0xD800, + // converted to bytes using utf-16be encoder flag. + var lead = convertCodeUnitToBytes( + ((code_point - 0x10000) >> 10) + 0xD800, utf16_be); + + // 4. Let trail be ((code point − 0x10000) & 0x3FF) + 0xDC00, + // converted to bytes using utf-16be encoder flag. + var trail = convertCodeUnitToBytes( + ((code_point - 0x10000) & 0x3FF) + 0xDC00, utf16_be); + + // 5. Return a byte sequence of lead followed by trail. + return lead.concat(trail); + }; + } + + // 14.3 utf-16be + /** @param {{fatal: boolean}} options */ + encoders['utf-16be'] = function(options) { + return new UTF16Encoder(true, options); + }; + /** @param {{fatal: boolean}} options */ + decoders['utf-16be'] = function(options) { + return new UTF16Decoder(true, options); + }; + + // 14.4 utf-16le + /** @param {{fatal: boolean}} options */ + encoders['utf-16le'] = function(options) { + return new UTF16Encoder(false, options); + }; + /** @param {{fatal: boolean}} options */ + decoders['utf-16le'] = function(options) { + return new UTF16Decoder(false, options); + }; + + // 14.5 x-user-defined + + /** + * @constructor + * @implements {Decoder} + * @param {{fatal: boolean}} options + */ + function XUserDefinedDecoder(options) { + var fatal = options.fatal; + /** + * @param {Stream} stream The stream of bytes being decoded. + * @param {number} bite The next byte read from the stream. + * @return {?(number|!Array.)} The next code point(s) + * decoded, or null if not enough data exists in the input + * stream to decode a complete code point. + */ + this.handler = function(stream, bite) { + // 1. If byte is end-of-stream, return finished. + if (bite === end_of_stream) + return finished; + + // 2. If byte is in the range 0x00 to 0x7F, return a code point + // whose value is byte. + if (inRange(bite, 0x00, 0x7F)) + return bite; + + // 3. Return a code point whose value is 0xF780 + byte − 0x80. + return 0xF780 + bite - 0x80; + }; + } + + /** + * @constructor + * @implements {Encoder} + * @param {{fatal: boolean}} options + */ + function XUserDefinedEncoder(options) { + var fatal = options.fatal; + /** + * @param {Stream} stream Input stream. + * @param {number} code_point Next code point read from the stream. + * @return {(number|!Array.)} Byte(s) to emit. + */ + this.handler = function(stream, code_point) { + // 1.If code point is end-of-stream, return finished. + if (code_point === end_of_stream) + return finished; + + // 2. If code point is in the range U+0000 to U+007F, return a + // byte whose value is code point. + if (inRange(code_point, 0x0000, 0x007F)) + return code_point; + + // 3. If code point is in the range U+F780 to U+F7FF, return a + // byte whose value is code point − 0xF780 + 0x80. + if (inRange(code_point, 0xF780, 0xF7FF)) + return code_point - 0xF780 + 0x80; + + // 4. Return error with code point. + return encoderError(code_point); + }; + } + + /** @param {{fatal: boolean}} options */ + encoders['x-user-defined'] = function(options) { + return new XUserDefinedEncoder(options); + }; + /** @param {{fatal: boolean}} options */ + decoders['x-user-defined'] = function(options) { + return new XUserDefinedDecoder(options); + }; + + if (!global['TextEncoder']) + global['TextEncoder'] = TextEncoder; + if (!global['TextDecoder']) + global['TextDecoder'] = TextDecoder; +}(this)); diff --git a/examples/echo/ws-echo/js-client/index.html b/examples/echo/ws-echo/js-client/index.html new file mode 100644 index 00000000..ae559e67 --- /dev/null +++ b/examples/echo/ws-echo/js-client/index.html @@ -0,0 +1,23 @@ + + + + + Echo Example + + + +
    +
  • +
    +
      +
      + +
    • +
    + + + + + + + diff --git a/examples/echo/ws-echo/js-client/jquery-1.10.2.min.js b/examples/echo/ws-echo/js-client/jquery-1.10.2.min.js new file mode 100644 index 00000000..da417064 --- /dev/null +++ b/examples/echo/ws-echo/js-client/jquery-1.10.2.min.js @@ -0,0 +1,6 @@ +/*! jQuery v1.10.2 | (c) 2005, 2013 jQuery Foundation, Inc. | jquery.org/license +//@ sourceMappingURL=jquery-1.10.2.min.map +*/ +(function(e,t){var n,r,i=typeof t,o=e.location,a=e.document,s=a.documentElement,l=e.jQuery,u=e.$,c={},p=[],f="1.10.2",d=p.concat,h=p.push,g=p.slice,m=p.indexOf,y=c.toString,v=c.hasOwnProperty,b=f.trim,x=function(e,t){return new x.fn.init(e,t,r)},w=/[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/.source,T=/\S+/g,C=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,N=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,k=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,E=/^[\],:{}\s]*$/,S=/(?:^|:|,)(?:\s*\[)+/g,A=/\\(?:["\\\/bfnrt]|u[\da-fA-F]{4})/g,j=/"[^"\\\r\n]*"|true|false|null|-?(?:\d+\.|)\d+(?:[eE][+-]?\d+|)/g,D=/^-ms-/,L=/-([\da-z])/gi,H=function(e,t){return t.toUpperCase()},q=function(e){(a.addEventListener||"load"===e.type||"complete"===a.readyState)&&(_(),x.ready())},_=function(){a.addEventListener?(a.removeEventListener("DOMContentLoaded",q,!1),e.removeEventListener("load",q,!1)):(a.detachEvent("onreadystatechange",q),e.detachEvent("onload",q))};x.fn=x.prototype={jquery:f,constructor:x,init:function(e,n,r){var i,o;if(!e)return this;if("string"==typeof e){if(i="<"===e.charAt(0)&&">"===e.charAt(e.length-1)&&e.length>=3?[null,e,null]:N.exec(e),!i||!i[1]&&n)return!n||n.jquery?(n||r).find(e):this.constructor(n).find(e);if(i[1]){if(n=n instanceof x?n[0]:n,x.merge(this,x.parseHTML(i[1],n&&n.nodeType?n.ownerDocument||n:a,!0)),k.test(i[1])&&x.isPlainObject(n))for(i in n)x.isFunction(this[i])?this[i](n[i]):this.attr(i,n[i]);return this}if(o=a.getElementById(i[2]),o&&o.parentNode){if(o.id!==i[2])return r.find(e);this.length=1,this[0]=o}return this.context=a,this.selector=e,this}return e.nodeType?(this.context=this[0]=e,this.length=1,this):x.isFunction(e)?r.ready(e):(e.selector!==t&&(this.selector=e.selector,this.context=e.context),x.makeArray(e,this))},selector:"",length:0,toArray:function(){return g.call(this)},get:function(e){return null==e?this.toArray():0>e?this[this.length+e]:this[e]},pushStack:function(e){var t=x.merge(this.constructor(),e);return t.prevObject=this,t.context=this.context,t},each:function(e,t){return x.each(this,e,t)},ready:function(e){return x.ready.promise().done(e),this},slice:function(){return this.pushStack(g.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(e){var t=this.length,n=+e+(0>e?t:0);return this.pushStack(n>=0&&t>n?[this[n]]:[])},map:function(e){return this.pushStack(x.map(this,function(t,n){return e.call(t,n,t)}))},end:function(){return this.prevObject||this.constructor(null)},push:h,sort:[].sort,splice:[].splice},x.fn.init.prototype=x.fn,x.extend=x.fn.extend=function(){var e,n,r,i,o,a,s=arguments[0]||{},l=1,u=arguments.length,c=!1;for("boolean"==typeof s&&(c=s,s=arguments[1]||{},l=2),"object"==typeof s||x.isFunction(s)||(s={}),u===l&&(s=this,--l);u>l;l++)if(null!=(o=arguments[l]))for(i in o)e=s[i],r=o[i],s!==r&&(c&&r&&(x.isPlainObject(r)||(n=x.isArray(r)))?(n?(n=!1,a=e&&x.isArray(e)?e:[]):a=e&&x.isPlainObject(e)?e:{},s[i]=x.extend(c,a,r)):r!==t&&(s[i]=r));return s},x.extend({expando:"jQuery"+(f+Math.random()).replace(/\D/g,""),noConflict:function(t){return e.$===x&&(e.$=u),t&&e.jQuery===x&&(e.jQuery=l),x},isReady:!1,readyWait:1,holdReady:function(e){e?x.readyWait++:x.ready(!0)},ready:function(e){if(e===!0?!--x.readyWait:!x.isReady){if(!a.body)return setTimeout(x.ready);x.isReady=!0,e!==!0&&--x.readyWait>0||(n.resolveWith(a,[x]),x.fn.trigger&&x(a).trigger("ready").off("ready"))}},isFunction:function(e){return"function"===x.type(e)},isArray:Array.isArray||function(e){return"array"===x.type(e)},isWindow:function(e){return null!=e&&e==e.window},isNumeric:function(e){return!isNaN(parseFloat(e))&&isFinite(e)},type:function(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?c[y.call(e)]||"object":typeof e},isPlainObject:function(e){var n;if(!e||"object"!==x.type(e)||e.nodeType||x.isWindow(e))return!1;try{if(e.constructor&&!v.call(e,"constructor")&&!v.call(e.constructor.prototype,"isPrototypeOf"))return!1}catch(r){return!1}if(x.support.ownLast)for(n in e)return v.call(e,n);for(n in e);return n===t||v.call(e,n)},isEmptyObject:function(e){var t;for(t in e)return!1;return!0},error:function(e){throw Error(e)},parseHTML:function(e,t,n){if(!e||"string"!=typeof e)return null;"boolean"==typeof t&&(n=t,t=!1),t=t||a;var r=k.exec(e),i=!n&&[];return r?[t.createElement(r[1])]:(r=x.buildFragment([e],t,i),i&&x(i).remove(),x.merge([],r.childNodes))},parseJSON:function(n){return e.JSON&&e.JSON.parse?e.JSON.parse(n):null===n?n:"string"==typeof n&&(n=x.trim(n),n&&E.test(n.replace(A,"@").replace(j,"]").replace(S,"")))?Function("return "+n)():(x.error("Invalid JSON: "+n),t)},parseXML:function(n){var r,i;if(!n||"string"!=typeof n)return null;try{e.DOMParser?(i=new DOMParser,r=i.parseFromString(n,"text/xml")):(r=new ActiveXObject("Microsoft.XMLDOM"),r.async="false",r.loadXML(n))}catch(o){r=t}return r&&r.documentElement&&!r.getElementsByTagName("parsererror").length||x.error("Invalid XML: "+n),r},noop:function(){},globalEval:function(t){t&&x.trim(t)&&(e.execScript||function(t){e.eval.call(e,t)})(t)},camelCase:function(e){return e.replace(D,"ms-").replace(L,H)},nodeName:function(e,t){return e.nodeName&&e.nodeName.toLowerCase()===t.toLowerCase()},each:function(e,t,n){var r,i=0,o=e.length,a=M(e);if(n){if(a){for(;o>i;i++)if(r=t.apply(e[i],n),r===!1)break}else for(i in e)if(r=t.apply(e[i],n),r===!1)break}else if(a){for(;o>i;i++)if(r=t.call(e[i],i,e[i]),r===!1)break}else for(i in e)if(r=t.call(e[i],i,e[i]),r===!1)break;return e},trim:b&&!b.call("\ufeff\u00a0")?function(e){return null==e?"":b.call(e)}:function(e){return null==e?"":(e+"").replace(C,"")},makeArray:function(e,t){var n=t||[];return null!=e&&(M(Object(e))?x.merge(n,"string"==typeof e?[e]:e):h.call(n,e)),n},inArray:function(e,t,n){var r;if(t){if(m)return m.call(t,e,n);for(r=t.length,n=n?0>n?Math.max(0,r+n):n:0;r>n;n++)if(n in t&&t[n]===e)return n}return-1},merge:function(e,n){var r=n.length,i=e.length,o=0;if("number"==typeof r)for(;r>o;o++)e[i++]=n[o];else while(n[o]!==t)e[i++]=n[o++];return e.length=i,e},grep:function(e,t,n){var r,i=[],o=0,a=e.length;for(n=!!n;a>o;o++)r=!!t(e[o],o),n!==r&&i.push(e[o]);return i},map:function(e,t,n){var r,i=0,o=e.length,a=M(e),s=[];if(a)for(;o>i;i++)r=t(e[i],i,n),null!=r&&(s[s.length]=r);else for(i in e)r=t(e[i],i,n),null!=r&&(s[s.length]=r);return d.apply([],s)},guid:1,proxy:function(e,n){var r,i,o;return"string"==typeof n&&(o=e[n],n=e,e=o),x.isFunction(e)?(r=g.call(arguments,2),i=function(){return e.apply(n||this,r.concat(g.call(arguments)))},i.guid=e.guid=e.guid||x.guid++,i):t},access:function(e,n,r,i,o,a,s){var l=0,u=e.length,c=null==r;if("object"===x.type(r)){o=!0;for(l in r)x.access(e,n,l,r[l],!0,a,s)}else if(i!==t&&(o=!0,x.isFunction(i)||(s=!0),c&&(s?(n.call(e,i),n=null):(c=n,n=function(e,t,n){return c.call(x(e),n)})),n))for(;u>l;l++)n(e[l],r,s?i:i.call(e[l],l,n(e[l],r)));return o?e:c?n.call(e):u?n(e[0],r):a},now:function(){return(new Date).getTime()},swap:function(e,t,n,r){var i,o,a={};for(o in t)a[o]=e.style[o],e.style[o]=t[o];i=n.apply(e,r||[]);for(o in t)e.style[o]=a[o];return i}}),x.ready.promise=function(t){if(!n)if(n=x.Deferred(),"complete"===a.readyState)setTimeout(x.ready);else if(a.addEventListener)a.addEventListener("DOMContentLoaded",q,!1),e.addEventListener("load",q,!1);else{a.attachEvent("onreadystatechange",q),e.attachEvent("onload",q);var r=!1;try{r=null==e.frameElement&&a.documentElement}catch(i){}r&&r.doScroll&&function o(){if(!x.isReady){try{r.doScroll("left")}catch(e){return setTimeout(o,50)}_(),x.ready()}}()}return n.promise(t)},x.each("Boolean Number String Function Array Date RegExp Object Error".split(" "),function(e,t){c["[object "+t+"]"]=t.toLowerCase()});function M(e){var t=e.length,n=x.type(e);return x.isWindow(e)?!1:1===e.nodeType&&t?!0:"array"===n||"function"!==n&&(0===t||"number"==typeof t&&t>0&&t-1 in e)}r=x(a),function(e,t){var n,r,i,o,a,s,l,u,c,p,f,d,h,g,m,y,v,b="sizzle"+-new Date,w=e.document,T=0,C=0,N=st(),k=st(),E=st(),S=!1,A=function(e,t){return e===t?(S=!0,0):0},j=typeof t,D=1<<31,L={}.hasOwnProperty,H=[],q=H.pop,_=H.push,M=H.push,O=H.slice,F=H.indexOf||function(e){var t=0,n=this.length;for(;n>t;t++)if(this[t]===e)return t;return-1},B="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",P="[\\x20\\t\\r\\n\\f]",R="(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",W=R.replace("w","w#"),$="\\["+P+"*("+R+")"+P+"*(?:([*^$|!~]?=)"+P+"*(?:(['\"])((?:\\\\.|[^\\\\])*?)\\3|("+W+")|)|)"+P+"*\\]",I=":("+R+")(?:\\(((['\"])((?:\\\\.|[^\\\\])*?)\\3|((?:\\\\.|[^\\\\()[\\]]|"+$.replace(3,8)+")*)|.*)\\)|)",z=RegExp("^"+P+"+|((?:^|[^\\\\])(?:\\\\.)*)"+P+"+$","g"),X=RegExp("^"+P+"*,"+P+"*"),U=RegExp("^"+P+"*([>+~]|"+P+")"+P+"*"),V=RegExp(P+"*[+~]"),Y=RegExp("="+P+"*([^\\]'\"]*)"+P+"*\\]","g"),J=RegExp(I),G=RegExp("^"+W+"$"),Q={ID:RegExp("^#("+R+")"),CLASS:RegExp("^\\.("+R+")"),TAG:RegExp("^("+R.replace("w","w*")+")"),ATTR:RegExp("^"+$),PSEUDO:RegExp("^"+I),CHILD:RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+P+"*(even|odd|(([+-]|)(\\d*)n|)"+P+"*(?:([+-]|)"+P+"*(\\d+)|))"+P+"*\\)|)","i"),bool:RegExp("^(?:"+B+")$","i"),needsContext:RegExp("^"+P+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+P+"*((?:-\\d)?\\d*)"+P+"*\\)|)(?=[^-]|$)","i")},K=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,et=/^(?:input|select|textarea|button)$/i,tt=/^h\d$/i,nt=/'|\\/g,rt=RegExp("\\\\([\\da-f]{1,6}"+P+"?|("+P+")|.)","ig"),it=function(e,t,n){var r="0x"+t-65536;return r!==r||n?t:0>r?String.fromCharCode(r+65536):String.fromCharCode(55296|r>>10,56320|1023&r)};try{M.apply(H=O.call(w.childNodes),w.childNodes),H[w.childNodes.length].nodeType}catch(ot){M={apply:H.length?function(e,t){_.apply(e,O.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function at(e,t,n,i){var o,a,s,l,u,c,d,m,y,x;if((t?t.ownerDocument||t:w)!==f&&p(t),t=t||f,n=n||[],!e||"string"!=typeof e)return n;if(1!==(l=t.nodeType)&&9!==l)return[];if(h&&!i){if(o=Z.exec(e))if(s=o[1]){if(9===l){if(a=t.getElementById(s),!a||!a.parentNode)return n;if(a.id===s)return n.push(a),n}else if(t.ownerDocument&&(a=t.ownerDocument.getElementById(s))&&v(t,a)&&a.id===s)return n.push(a),n}else{if(o[2])return M.apply(n,t.getElementsByTagName(e)),n;if((s=o[3])&&r.getElementsByClassName&&t.getElementsByClassName)return M.apply(n,t.getElementsByClassName(s)),n}if(r.qsa&&(!g||!g.test(e))){if(m=d=b,y=t,x=9===l&&e,1===l&&"object"!==t.nodeName.toLowerCase()){c=mt(e),(d=t.getAttribute("id"))?m=d.replace(nt,"\\$&"):t.setAttribute("id",m),m="[id='"+m+"'] ",u=c.length;while(u--)c[u]=m+yt(c[u]);y=V.test(e)&&t.parentNode||t,x=c.join(",")}if(x)try{return M.apply(n,y.querySelectorAll(x)),n}catch(T){}finally{d||t.removeAttribute("id")}}}return kt(e.replace(z,"$1"),t,n,i)}function st(){var e=[];function t(n,r){return e.push(n+=" ")>o.cacheLength&&delete t[e.shift()],t[n]=r}return t}function lt(e){return e[b]=!0,e}function ut(e){var t=f.createElement("div");try{return!!e(t)}catch(n){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function ct(e,t){var n=e.split("|"),r=e.length;while(r--)o.attrHandle[n[r]]=t}function pt(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&(~t.sourceIndex||D)-(~e.sourceIndex||D);if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function ft(e){return function(t){var n=t.nodeName.toLowerCase();return"input"===n&&t.type===e}}function dt(e){return function(t){var n=t.nodeName.toLowerCase();return("input"===n||"button"===n)&&t.type===e}}function ht(e){return lt(function(t){return t=+t,lt(function(n,r){var i,o=e([],n.length,t),a=o.length;while(a--)n[i=o[a]]&&(n[i]=!(r[i]=n[i]))})})}s=at.isXML=function(e){var t=e&&(e.ownerDocument||e).documentElement;return t?"HTML"!==t.nodeName:!1},r=at.support={},p=at.setDocument=function(e){var n=e?e.ownerDocument||e:w,i=n.defaultView;return n!==f&&9===n.nodeType&&n.documentElement?(f=n,d=n.documentElement,h=!s(n),i&&i.attachEvent&&i!==i.top&&i.attachEvent("onbeforeunload",function(){p()}),r.attributes=ut(function(e){return e.className="i",!e.getAttribute("className")}),r.getElementsByTagName=ut(function(e){return e.appendChild(n.createComment("")),!e.getElementsByTagName("*").length}),r.getElementsByClassName=ut(function(e){return e.innerHTML="
    ",e.firstChild.className="i",2===e.getElementsByClassName("i").length}),r.getById=ut(function(e){return d.appendChild(e).id=b,!n.getElementsByName||!n.getElementsByName(b).length}),r.getById?(o.find.ID=function(e,t){if(typeof t.getElementById!==j&&h){var n=t.getElementById(e);return n&&n.parentNode?[n]:[]}},o.filter.ID=function(e){var t=e.replace(rt,it);return function(e){return e.getAttribute("id")===t}}):(delete o.find.ID,o.filter.ID=function(e){var t=e.replace(rt,it);return function(e){var n=typeof e.getAttributeNode!==j&&e.getAttributeNode("id");return n&&n.value===t}}),o.find.TAG=r.getElementsByTagName?function(e,n){return typeof n.getElementsByTagName!==j?n.getElementsByTagName(e):t}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},o.find.CLASS=r.getElementsByClassName&&function(e,n){return typeof n.getElementsByClassName!==j&&h?n.getElementsByClassName(e):t},m=[],g=[],(r.qsa=K.test(n.querySelectorAll))&&(ut(function(e){e.innerHTML="",e.querySelectorAll("[selected]").length||g.push("\\["+P+"*(?:value|"+B+")"),e.querySelectorAll(":checked").length||g.push(":checked")}),ut(function(e){var t=n.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("t",""),e.querySelectorAll("[t^='']").length&&g.push("[*^$]="+P+"*(?:''|\"\")"),e.querySelectorAll(":enabled").length||g.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),g.push(",.*:")})),(r.matchesSelector=K.test(y=d.webkitMatchesSelector||d.mozMatchesSelector||d.oMatchesSelector||d.msMatchesSelector))&&ut(function(e){r.disconnectedMatch=y.call(e,"div"),y.call(e,"[s!='']:x"),m.push("!=",I)}),g=g.length&&RegExp(g.join("|")),m=m.length&&RegExp(m.join("|")),v=K.test(d.contains)||d.compareDocumentPosition?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},A=d.compareDocumentPosition?function(e,t){if(e===t)return S=!0,0;var i=t.compareDocumentPosition&&e.compareDocumentPosition&&e.compareDocumentPosition(t);return i?1&i||!r.sortDetached&&t.compareDocumentPosition(e)===i?e===n||v(w,e)?-1:t===n||v(w,t)?1:c?F.call(c,e)-F.call(c,t):0:4&i?-1:1:e.compareDocumentPosition?-1:1}:function(e,t){var r,i=0,o=e.parentNode,a=t.parentNode,s=[e],l=[t];if(e===t)return S=!0,0;if(!o||!a)return e===n?-1:t===n?1:o?-1:a?1:c?F.call(c,e)-F.call(c,t):0;if(o===a)return pt(e,t);r=e;while(r=r.parentNode)s.unshift(r);r=t;while(r=r.parentNode)l.unshift(r);while(s[i]===l[i])i++;return i?pt(s[i],l[i]):s[i]===w?-1:l[i]===w?1:0},n):f},at.matches=function(e,t){return at(e,null,null,t)},at.matchesSelector=function(e,t){if((e.ownerDocument||e)!==f&&p(e),t=t.replace(Y,"='$1']"),!(!r.matchesSelector||!h||m&&m.test(t)||g&&g.test(t)))try{var n=y.call(e,t);if(n||r.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(i){}return at(t,f,null,[e]).length>0},at.contains=function(e,t){return(e.ownerDocument||e)!==f&&p(e),v(e,t)},at.attr=function(e,n){(e.ownerDocument||e)!==f&&p(e);var i=o.attrHandle[n.toLowerCase()],a=i&&L.call(o.attrHandle,n.toLowerCase())?i(e,n,!h):t;return a===t?r.attributes||!h?e.getAttribute(n):(a=e.getAttributeNode(n))&&a.specified?a.value:null:a},at.error=function(e){throw Error("Syntax error, unrecognized expression: "+e)},at.uniqueSort=function(e){var t,n=[],i=0,o=0;if(S=!r.detectDuplicates,c=!r.sortStable&&e.slice(0),e.sort(A),S){while(t=e[o++])t===e[o]&&(i=n.push(o));while(i--)e.splice(n[i],1)}return e},a=at.getText=function(e){var t,n="",r=0,i=e.nodeType;if(i){if(1===i||9===i||11===i){if("string"==typeof e.textContent)return e.textContent;for(e=e.firstChild;e;e=e.nextSibling)n+=a(e)}else if(3===i||4===i)return e.nodeValue}else for(;t=e[r];r++)n+=a(t);return n},o=at.selectors={cacheLength:50,createPseudo:lt,match:Q,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(rt,it),e[3]=(e[4]||e[5]||"").replace(rt,it),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||at.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&at.error(e[0]),e},PSEUDO:function(e){var n,r=!e[5]&&e[2];return Q.CHILD.test(e[0])?null:(e[3]&&e[4]!==t?e[2]=e[4]:r&&J.test(r)&&(n=mt(r,!0))&&(n=r.indexOf(")",r.length-n)-r.length)&&(e[0]=e[0].slice(0,n),e[2]=r.slice(0,n)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(rt,it).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=N[e+" "];return t||(t=RegExp("(^|"+P+")"+e+"("+P+"|$)"))&&N(e,function(e){return t.test("string"==typeof e.className&&e.className||typeof e.getAttribute!==j&&e.getAttribute("class")||"")})},ATTR:function(e,t,n){return function(r){var i=at.attr(r,e);return null==i?"!="===t:t?(i+="","="===t?i===n:"!="===t?i!==n:"^="===t?n&&0===i.indexOf(n):"*="===t?n&&i.indexOf(n)>-1:"$="===t?n&&i.slice(-n.length)===n:"~="===t?(" "+i+" ").indexOf(n)>-1:"|="===t?i===n||i.slice(0,n.length+1)===n+"-":!1):!0}},CHILD:function(e,t,n,r,i){var o="nth"!==e.slice(0,3),a="last"!==e.slice(-4),s="of-type"===t;return 1===r&&0===i?function(e){return!!e.parentNode}:function(t,n,l){var u,c,p,f,d,h,g=o!==a?"nextSibling":"previousSibling",m=t.parentNode,y=s&&t.nodeName.toLowerCase(),v=!l&&!s;if(m){if(o){while(g){p=t;while(p=p[g])if(s?p.nodeName.toLowerCase()===y:1===p.nodeType)return!1;h=g="only"===e&&!h&&"nextSibling"}return!0}if(h=[a?m.firstChild:m.lastChild],a&&v){c=m[b]||(m[b]={}),u=c[e]||[],d=u[0]===T&&u[1],f=u[0]===T&&u[2],p=d&&m.childNodes[d];while(p=++d&&p&&p[g]||(f=d=0)||h.pop())if(1===p.nodeType&&++f&&p===t){c[e]=[T,d,f];break}}else if(v&&(u=(t[b]||(t[b]={}))[e])&&u[0]===T)f=u[1];else while(p=++d&&p&&p[g]||(f=d=0)||h.pop())if((s?p.nodeName.toLowerCase()===y:1===p.nodeType)&&++f&&(v&&((p[b]||(p[b]={}))[e]=[T,f]),p===t))break;return f-=i,f===r||0===f%r&&f/r>=0}}},PSEUDO:function(e,t){var n,r=o.pseudos[e]||o.setFilters[e.toLowerCase()]||at.error("unsupported pseudo: "+e);return r[b]?r(t):r.length>1?(n=[e,e,"",t],o.setFilters.hasOwnProperty(e.toLowerCase())?lt(function(e,n){var i,o=r(e,t),a=o.length;while(a--)i=F.call(e,o[a]),e[i]=!(n[i]=o[a])}):function(e){return r(e,0,n)}):r}},pseudos:{not:lt(function(e){var t=[],n=[],r=l(e.replace(z,"$1"));return r[b]?lt(function(e,t,n,i){var o,a=r(e,null,i,[]),s=e.length;while(s--)(o=a[s])&&(e[s]=!(t[s]=o))}):function(e,i,o){return t[0]=e,r(t,null,o,n),!n.pop()}}),has:lt(function(e){return function(t){return at(e,t).length>0}}),contains:lt(function(e){return function(t){return(t.textContent||t.innerText||a(t)).indexOf(e)>-1}}),lang:lt(function(e){return G.test(e||"")||at.error("unsupported lang: "+e),e=e.replace(rt,it).toLowerCase(),function(t){var n;do if(n=h?t.lang:t.getAttribute("xml:lang")||t.getAttribute("lang"))return n=n.toLowerCase(),n===e||0===n.indexOf(e+"-");while((t=t.parentNode)&&1===t.nodeType);return!1}}),target:function(t){var n=e.location&&e.location.hash;return n&&n.slice(1)===t.id},root:function(e){return e===d},focus:function(e){return e===f.activeElement&&(!f.hasFocus||f.hasFocus())&&!!(e.type||e.href||~e.tabIndex)},enabled:function(e){return e.disabled===!1},disabled:function(e){return e.disabled===!0},checked:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&!!e.checked||"option"===t&&!!e.selected},selected:function(e){return e.parentNode&&e.parentNode.selectedIndex,e.selected===!0},empty:function(e){for(e=e.firstChild;e;e=e.nextSibling)if(e.nodeName>"@"||3===e.nodeType||4===e.nodeType)return!1;return!0},parent:function(e){return!o.pseudos.empty(e)},header:function(e){return tt.test(e.nodeName)},input:function(e){return et.test(e.nodeName)},button:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&"button"===e.type||"button"===t},text:function(e){var t;return"input"===e.nodeName.toLowerCase()&&"text"===e.type&&(null==(t=e.getAttribute("type"))||t.toLowerCase()===e.type)},first:ht(function(){return[0]}),last:ht(function(e,t){return[t-1]}),eq:ht(function(e,t,n){return[0>n?n+t:n]}),even:ht(function(e,t){var n=0;for(;t>n;n+=2)e.push(n);return e}),odd:ht(function(e,t){var n=1;for(;t>n;n+=2)e.push(n);return e}),lt:ht(function(e,t,n){var r=0>n?n+t:n;for(;--r>=0;)e.push(r);return e}),gt:ht(function(e,t,n){var r=0>n?n+t:n;for(;t>++r;)e.push(r);return e})}},o.pseudos.nth=o.pseudos.eq;for(n in{radio:!0,checkbox:!0,file:!0,password:!0,image:!0})o.pseudos[n]=ft(n);for(n in{submit:!0,reset:!0})o.pseudos[n]=dt(n);function gt(){}gt.prototype=o.filters=o.pseudos,o.setFilters=new gt;function mt(e,t){var n,r,i,a,s,l,u,c=k[e+" "];if(c)return t?0:c.slice(0);s=e,l=[],u=o.preFilter;while(s){(!n||(r=X.exec(s)))&&(r&&(s=s.slice(r[0].length)||s),l.push(i=[])),n=!1,(r=U.exec(s))&&(n=r.shift(),i.push({value:n,type:r[0].replace(z," ")}),s=s.slice(n.length));for(a in o.filter)!(r=Q[a].exec(s))||u[a]&&!(r=u[a](r))||(n=r.shift(),i.push({value:n,type:a,matches:r}),s=s.slice(n.length));if(!n)break}return t?s.length:s?at.error(e):k(e,l).slice(0)}function yt(e){var t=0,n=e.length,r="";for(;n>t;t++)r+=e[t].value;return r}function vt(e,t,n){var r=t.dir,o=n&&"parentNode"===r,a=C++;return t.first?function(t,n,i){while(t=t[r])if(1===t.nodeType||o)return e(t,n,i)}:function(t,n,s){var l,u,c,p=T+" "+a;if(s){while(t=t[r])if((1===t.nodeType||o)&&e(t,n,s))return!0}else while(t=t[r])if(1===t.nodeType||o)if(c=t[b]||(t[b]={}),(u=c[r])&&u[0]===p){if((l=u[1])===!0||l===i)return l===!0}else if(u=c[r]=[p],u[1]=e(t,n,s)||i,u[1]===!0)return!0}}function bt(e){return e.length>1?function(t,n,r){var i=e.length;while(i--)if(!e[i](t,n,r))return!1;return!0}:e[0]}function xt(e,t,n,r,i){var o,a=[],s=0,l=e.length,u=null!=t;for(;l>s;s++)(o=e[s])&&(!n||n(o,r,i))&&(a.push(o),u&&t.push(s));return a}function wt(e,t,n,r,i,o){return r&&!r[b]&&(r=wt(r)),i&&!i[b]&&(i=wt(i,o)),lt(function(o,a,s,l){var u,c,p,f=[],d=[],h=a.length,g=o||Nt(t||"*",s.nodeType?[s]:s,[]),m=!e||!o&&t?g:xt(g,f,e,s,l),y=n?i||(o?e:h||r)?[]:a:m;if(n&&n(m,y,s,l),r){u=xt(y,d),r(u,[],s,l),c=u.length;while(c--)(p=u[c])&&(y[d[c]]=!(m[d[c]]=p))}if(o){if(i||e){if(i){u=[],c=y.length;while(c--)(p=y[c])&&u.push(m[c]=p);i(null,y=[],u,l)}c=y.length;while(c--)(p=y[c])&&(u=i?F.call(o,p):f[c])>-1&&(o[u]=!(a[u]=p))}}else y=xt(y===a?y.splice(h,y.length):y),i?i(null,a,y,l):M.apply(a,y)})}function Tt(e){var t,n,r,i=e.length,a=o.relative[e[0].type],s=a||o.relative[" "],l=a?1:0,c=vt(function(e){return e===t},s,!0),p=vt(function(e){return F.call(t,e)>-1},s,!0),f=[function(e,n,r){return!a&&(r||n!==u)||((t=n).nodeType?c(e,n,r):p(e,n,r))}];for(;i>l;l++)if(n=o.relative[e[l].type])f=[vt(bt(f),n)];else{if(n=o.filter[e[l].type].apply(null,e[l].matches),n[b]){for(r=++l;i>r;r++)if(o.relative[e[r].type])break;return wt(l>1&&bt(f),l>1&&yt(e.slice(0,l-1).concat({value:" "===e[l-2].type?"*":""})).replace(z,"$1"),n,r>l&&Tt(e.slice(l,r)),i>r&&Tt(e=e.slice(r)),i>r&&yt(e))}f.push(n)}return bt(f)}function Ct(e,t){var n=0,r=t.length>0,a=e.length>0,s=function(s,l,c,p,d){var h,g,m,y=[],v=0,b="0",x=s&&[],w=null!=d,C=u,N=s||a&&o.find.TAG("*",d&&l.parentNode||l),k=T+=null==C?1:Math.random()||.1;for(w&&(u=l!==f&&l,i=n);null!=(h=N[b]);b++){if(a&&h){g=0;while(m=e[g++])if(m(h,l,c)){p.push(h);break}w&&(T=k,i=++n)}r&&((h=!m&&h)&&v--,s&&x.push(h))}if(v+=b,r&&b!==v){g=0;while(m=t[g++])m(x,y,l,c);if(s){if(v>0)while(b--)x[b]||y[b]||(y[b]=q.call(p));y=xt(y)}M.apply(p,y),w&&!s&&y.length>0&&v+t.length>1&&at.uniqueSort(p)}return w&&(T=k,u=C),x};return r?lt(s):s}l=at.compile=function(e,t){var n,r=[],i=[],o=E[e+" "];if(!o){t||(t=mt(e)),n=t.length;while(n--)o=Tt(t[n]),o[b]?r.push(o):i.push(o);o=E(e,Ct(i,r))}return o};function Nt(e,t,n){var r=0,i=t.length;for(;i>r;r++)at(e,t[r],n);return n}function kt(e,t,n,i){var a,s,u,c,p,f=mt(e);if(!i&&1===f.length){if(s=f[0]=f[0].slice(0),s.length>2&&"ID"===(u=s[0]).type&&r.getById&&9===t.nodeType&&h&&o.relative[s[1].type]){if(t=(o.find.ID(u.matches[0].replace(rt,it),t)||[])[0],!t)return n;e=e.slice(s.shift().value.length)}a=Q.needsContext.test(e)?0:s.length;while(a--){if(u=s[a],o.relative[c=u.type])break;if((p=o.find[c])&&(i=p(u.matches[0].replace(rt,it),V.test(s[0].type)&&t.parentNode||t))){if(s.splice(a,1),e=i.length&&yt(s),!e)return M.apply(n,i),n;break}}}return l(e,f)(i,t,!h,n,V.test(e)),n}r.sortStable=b.split("").sort(A).join("")===b,r.detectDuplicates=S,p(),r.sortDetached=ut(function(e){return 1&e.compareDocumentPosition(f.createElement("div"))}),ut(function(e){return e.innerHTML="","#"===e.firstChild.getAttribute("href")})||ct("type|href|height|width",function(e,n,r){return r?t:e.getAttribute(n,"type"===n.toLowerCase()?1:2)}),r.attributes&&ut(function(e){return e.innerHTML="",e.firstChild.setAttribute("value",""),""===e.firstChild.getAttribute("value")})||ct("value",function(e,n,r){return r||"input"!==e.nodeName.toLowerCase()?t:e.defaultValue}),ut(function(e){return null==e.getAttribute("disabled")})||ct(B,function(e,n,r){var i;return r?t:(i=e.getAttributeNode(n))&&i.specified?i.value:e[n]===!0?n.toLowerCase():null}),x.find=at,x.expr=at.selectors,x.expr[":"]=x.expr.pseudos,x.unique=at.uniqueSort,x.text=at.getText,x.isXMLDoc=at.isXML,x.contains=at.contains}(e);var O={};function F(e){var t=O[e]={};return x.each(e.match(T)||[],function(e,n){t[n]=!0}),t}x.Callbacks=function(e){e="string"==typeof e?O[e]||F(e):x.extend({},e);var n,r,i,o,a,s,l=[],u=!e.once&&[],c=function(t){for(r=e.memory&&t,i=!0,a=s||0,s=0,o=l.length,n=!0;l&&o>a;a++)if(l[a].apply(t[0],t[1])===!1&&e.stopOnFalse){r=!1;break}n=!1,l&&(u?u.length&&c(u.shift()):r?l=[]:p.disable())},p={add:function(){if(l){var t=l.length;(function i(t){x.each(t,function(t,n){var r=x.type(n);"function"===r?e.unique&&p.has(n)||l.push(n):n&&n.length&&"string"!==r&&i(n)})})(arguments),n?o=l.length:r&&(s=t,c(r))}return this},remove:function(){return l&&x.each(arguments,function(e,t){var r;while((r=x.inArray(t,l,r))>-1)l.splice(r,1),n&&(o>=r&&o--,a>=r&&a--)}),this},has:function(e){return e?x.inArray(e,l)>-1:!(!l||!l.length)},empty:function(){return l=[],o=0,this},disable:function(){return l=u=r=t,this},disabled:function(){return!l},lock:function(){return u=t,r||p.disable(),this},locked:function(){return!u},fireWith:function(e,t){return!l||i&&!u||(t=t||[],t=[e,t.slice?t.slice():t],n?u.push(t):c(t)),this},fire:function(){return p.fireWith(this,arguments),this},fired:function(){return!!i}};return p},x.extend({Deferred:function(e){var t=[["resolve","done",x.Callbacks("once memory"),"resolved"],["reject","fail",x.Callbacks("once memory"),"rejected"],["notify","progress",x.Callbacks("memory")]],n="pending",r={state:function(){return n},always:function(){return i.done(arguments).fail(arguments),this},then:function(){var e=arguments;return x.Deferred(function(n){x.each(t,function(t,o){var a=o[0],s=x.isFunction(e[t])&&e[t];i[o[1]](function(){var e=s&&s.apply(this,arguments);e&&x.isFunction(e.promise)?e.promise().done(n.resolve).fail(n.reject).progress(n.notify):n[a+"With"](this===r?n.promise():this,s?[e]:arguments)})}),e=null}).promise()},promise:function(e){return null!=e?x.extend(e,r):r}},i={};return r.pipe=r.then,x.each(t,function(e,o){var a=o[2],s=o[3];r[o[1]]=a.add,s&&a.add(function(){n=s},t[1^e][2].disable,t[2][2].lock),i[o[0]]=function(){return i[o[0]+"With"](this===i?r:this,arguments),this},i[o[0]+"With"]=a.fireWith}),r.promise(i),e&&e.call(i,i),i},when:function(e){var t=0,n=g.call(arguments),r=n.length,i=1!==r||e&&x.isFunction(e.promise)?r:0,o=1===i?e:x.Deferred(),a=function(e,t,n){return function(r){t[e]=this,n[e]=arguments.length>1?g.call(arguments):r,n===s?o.notifyWith(t,n):--i||o.resolveWith(t,n)}},s,l,u;if(r>1)for(s=Array(r),l=Array(r),u=Array(r);r>t;t++)n[t]&&x.isFunction(n[t].promise)?n[t].promise().done(a(t,u,n)).fail(o.reject).progress(a(t,l,s)):--i;return i||o.resolveWith(u,n),o.promise()}}),x.support=function(t){var n,r,o,s,l,u,c,p,f,d=a.createElement("div");if(d.setAttribute("className","t"),d.innerHTML="
    a",n=d.getElementsByTagName("*")||[],r=d.getElementsByTagName("a")[0],!r||!r.style||!n.length)return t;s=a.createElement("select"),u=s.appendChild(a.createElement("option")),o=d.getElementsByTagName("input")[0],r.style.cssText="top:1px;float:left;opacity:.5",t.getSetAttribute="t"!==d.className,t.leadingWhitespace=3===d.firstChild.nodeType,t.tbody=!d.getElementsByTagName("tbody").length,t.htmlSerialize=!!d.getElementsByTagName("link").length,t.style=/top/.test(r.getAttribute("style")),t.hrefNormalized="/a"===r.getAttribute("href"),t.opacity=/^0.5/.test(r.style.opacity),t.cssFloat=!!r.style.cssFloat,t.checkOn=!!o.value,t.optSelected=u.selected,t.enctype=!!a.createElement("form").enctype,t.html5Clone="<:nav>"!==a.createElement("nav").cloneNode(!0).outerHTML,t.inlineBlockNeedsLayout=!1,t.shrinkWrapBlocks=!1,t.pixelPosition=!1,t.deleteExpando=!0,t.noCloneEvent=!0,t.reliableMarginRight=!0,t.boxSizingReliable=!0,o.checked=!0,t.noCloneChecked=o.cloneNode(!0).checked,s.disabled=!0,t.optDisabled=!u.disabled;try{delete d.test}catch(h){t.deleteExpando=!1}o=a.createElement("input"),o.setAttribute("value",""),t.input=""===o.getAttribute("value"),o.value="t",o.setAttribute("type","radio"),t.radioValue="t"===o.value,o.setAttribute("checked","t"),o.setAttribute("name","t"),l=a.createDocumentFragment(),l.appendChild(o),t.appendChecked=o.checked,t.checkClone=l.cloneNode(!0).cloneNode(!0).lastChild.checked,d.attachEvent&&(d.attachEvent("onclick",function(){t.noCloneEvent=!1}),d.cloneNode(!0).click());for(f in{submit:!0,change:!0,focusin:!0})d.setAttribute(c="on"+f,"t"),t[f+"Bubbles"]=c in e||d.attributes[c].expando===!1;d.style.backgroundClip="content-box",d.cloneNode(!0).style.backgroundClip="",t.clearCloneStyle="content-box"===d.style.backgroundClip;for(f in x(t))break;return t.ownLast="0"!==f,x(function(){var n,r,o,s="padding:0;margin:0;border:0;display:block;box-sizing:content-box;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;",l=a.getElementsByTagName("body")[0];l&&(n=a.createElement("div"),n.style.cssText="border:0;width:0;height:0;position:absolute;top:0;left:-9999px;margin-top:1px",l.appendChild(n).appendChild(d),d.innerHTML="
    t
    ",o=d.getElementsByTagName("td"),o[0].style.cssText="padding:0;margin:0;border:0;display:none",p=0===o[0].offsetHeight,o[0].style.display="",o[1].style.display="none",t.reliableHiddenOffsets=p&&0===o[0].offsetHeight,d.innerHTML="",d.style.cssText="box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;padding:1px;border:1px;display:block;width:4px;margin-top:1%;position:absolute;top:1%;",x.swap(l,null!=l.style.zoom?{zoom:1}:{},function(){t.boxSizing=4===d.offsetWidth}),e.getComputedStyle&&(t.pixelPosition="1%"!==(e.getComputedStyle(d,null)||{}).top,t.boxSizingReliable="4px"===(e.getComputedStyle(d,null)||{width:"4px"}).width,r=d.appendChild(a.createElement("div")),r.style.cssText=d.style.cssText=s,r.style.marginRight=r.style.width="0",d.style.width="1px",t.reliableMarginRight=!parseFloat((e.getComputedStyle(r,null)||{}).marginRight)),typeof d.style.zoom!==i&&(d.innerHTML="",d.style.cssText=s+"width:1px;padding:1px;display:inline;zoom:1",t.inlineBlockNeedsLayout=3===d.offsetWidth,d.style.display="block",d.innerHTML="
    ",d.firstChild.style.width="5px",t.shrinkWrapBlocks=3!==d.offsetWidth,t.inlineBlockNeedsLayout&&(l.style.zoom=1)),l.removeChild(n),n=d=o=r=null)}),n=s=l=u=r=o=null,t +}({});var B=/(?:\{[\s\S]*\}|\[[\s\S]*\])$/,P=/([A-Z])/g;function R(e,n,r,i){if(x.acceptData(e)){var o,a,s=x.expando,l=e.nodeType,u=l?x.cache:e,c=l?e[s]:e[s]&&s;if(c&&u[c]&&(i||u[c].data)||r!==t||"string"!=typeof n)return c||(c=l?e[s]=p.pop()||x.guid++:s),u[c]||(u[c]=l?{}:{toJSON:x.noop}),("object"==typeof n||"function"==typeof n)&&(i?u[c]=x.extend(u[c],n):u[c].data=x.extend(u[c].data,n)),a=u[c],i||(a.data||(a.data={}),a=a.data),r!==t&&(a[x.camelCase(n)]=r),"string"==typeof n?(o=a[n],null==o&&(o=a[x.camelCase(n)])):o=a,o}}function W(e,t,n){if(x.acceptData(e)){var r,i,o=e.nodeType,a=o?x.cache:e,s=o?e[x.expando]:x.expando;if(a[s]){if(t&&(r=n?a[s]:a[s].data)){x.isArray(t)?t=t.concat(x.map(t,x.camelCase)):t in r?t=[t]:(t=x.camelCase(t),t=t in r?[t]:t.split(" ")),i=t.length;while(i--)delete r[t[i]];if(n?!I(r):!x.isEmptyObject(r))return}(n||(delete a[s].data,I(a[s])))&&(o?x.cleanData([e],!0):x.support.deleteExpando||a!=a.window?delete a[s]:a[s]=null)}}}x.extend({cache:{},noData:{applet:!0,embed:!0,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"},hasData:function(e){return e=e.nodeType?x.cache[e[x.expando]]:e[x.expando],!!e&&!I(e)},data:function(e,t,n){return R(e,t,n)},removeData:function(e,t){return W(e,t)},_data:function(e,t,n){return R(e,t,n,!0)},_removeData:function(e,t){return W(e,t,!0)},acceptData:function(e){if(e.nodeType&&1!==e.nodeType&&9!==e.nodeType)return!1;var t=e.nodeName&&x.noData[e.nodeName.toLowerCase()];return!t||t!==!0&&e.getAttribute("classid")===t}}),x.fn.extend({data:function(e,n){var r,i,o=null,a=0,s=this[0];if(e===t){if(this.length&&(o=x.data(s),1===s.nodeType&&!x._data(s,"parsedAttrs"))){for(r=s.attributes;r.length>a;a++)i=r[a].name,0===i.indexOf("data-")&&(i=x.camelCase(i.slice(5)),$(s,i,o[i]));x._data(s,"parsedAttrs",!0)}return o}return"object"==typeof e?this.each(function(){x.data(this,e)}):arguments.length>1?this.each(function(){x.data(this,e,n)}):s?$(s,e,x.data(s,e)):null},removeData:function(e){return this.each(function(){x.removeData(this,e)})}});function $(e,n,r){if(r===t&&1===e.nodeType){var i="data-"+n.replace(P,"-$1").toLowerCase();if(r=e.getAttribute(i),"string"==typeof r){try{r="true"===r?!0:"false"===r?!1:"null"===r?null:+r+""===r?+r:B.test(r)?x.parseJSON(r):r}catch(o){}x.data(e,n,r)}else r=t}return r}function I(e){var t;for(t in e)if(("data"!==t||!x.isEmptyObject(e[t]))&&"toJSON"!==t)return!1;return!0}x.extend({queue:function(e,n,r){var i;return e?(n=(n||"fx")+"queue",i=x._data(e,n),r&&(!i||x.isArray(r)?i=x._data(e,n,x.makeArray(r)):i.push(r)),i||[]):t},dequeue:function(e,t){t=t||"fx";var n=x.queue(e,t),r=n.length,i=n.shift(),o=x._queueHooks(e,t),a=function(){x.dequeue(e,t)};"inprogress"===i&&(i=n.shift(),r--),i&&("fx"===t&&n.unshift("inprogress"),delete o.stop,i.call(e,a,o)),!r&&o&&o.empty.fire()},_queueHooks:function(e,t){var n=t+"queueHooks";return x._data(e,n)||x._data(e,n,{empty:x.Callbacks("once memory").add(function(){x._removeData(e,t+"queue"),x._removeData(e,n)})})}}),x.fn.extend({queue:function(e,n){var r=2;return"string"!=typeof e&&(n=e,e="fx",r--),r>arguments.length?x.queue(this[0],e):n===t?this:this.each(function(){var t=x.queue(this,e,n);x._queueHooks(this,e),"fx"===e&&"inprogress"!==t[0]&&x.dequeue(this,e)})},dequeue:function(e){return this.each(function(){x.dequeue(this,e)})},delay:function(e,t){return e=x.fx?x.fx.speeds[e]||e:e,t=t||"fx",this.queue(t,function(t,n){var r=setTimeout(t,e);n.stop=function(){clearTimeout(r)}})},clearQueue:function(e){return this.queue(e||"fx",[])},promise:function(e,n){var r,i=1,o=x.Deferred(),a=this,s=this.length,l=function(){--i||o.resolveWith(a,[a])};"string"!=typeof e&&(n=e,e=t),e=e||"fx";while(s--)r=x._data(a[s],e+"queueHooks"),r&&r.empty&&(i++,r.empty.add(l));return l(),o.promise(n)}});var z,X,U=/[\t\r\n\f]/g,V=/\r/g,Y=/^(?:input|select|textarea|button|object)$/i,J=/^(?:a|area)$/i,G=/^(?:checked|selected)$/i,Q=x.support.getSetAttribute,K=x.support.input;x.fn.extend({attr:function(e,t){return x.access(this,x.attr,e,t,arguments.length>1)},removeAttr:function(e){return this.each(function(){x.removeAttr(this,e)})},prop:function(e,t){return x.access(this,x.prop,e,t,arguments.length>1)},removeProp:function(e){return e=x.propFix[e]||e,this.each(function(){try{this[e]=t,delete this[e]}catch(n){}})},addClass:function(e){var t,n,r,i,o,a=0,s=this.length,l="string"==typeof e&&e;if(x.isFunction(e))return this.each(function(t){x(this).addClass(e.call(this,t,this.className))});if(l)for(t=(e||"").match(T)||[];s>a;a++)if(n=this[a],r=1===n.nodeType&&(n.className?(" "+n.className+" ").replace(U," "):" ")){o=0;while(i=t[o++])0>r.indexOf(" "+i+" ")&&(r+=i+" ");n.className=x.trim(r)}return this},removeClass:function(e){var t,n,r,i,o,a=0,s=this.length,l=0===arguments.length||"string"==typeof e&&e;if(x.isFunction(e))return this.each(function(t){x(this).removeClass(e.call(this,t,this.className))});if(l)for(t=(e||"").match(T)||[];s>a;a++)if(n=this[a],r=1===n.nodeType&&(n.className?(" "+n.className+" ").replace(U," "):"")){o=0;while(i=t[o++])while(r.indexOf(" "+i+" ")>=0)r=r.replace(" "+i+" "," ");n.className=e?x.trim(r):""}return this},toggleClass:function(e,t){var n=typeof e;return"boolean"==typeof t&&"string"===n?t?this.addClass(e):this.removeClass(e):x.isFunction(e)?this.each(function(n){x(this).toggleClass(e.call(this,n,this.className,t),t)}):this.each(function(){if("string"===n){var t,r=0,o=x(this),a=e.match(T)||[];while(t=a[r++])o.hasClass(t)?o.removeClass(t):o.addClass(t)}else(n===i||"boolean"===n)&&(this.className&&x._data(this,"__className__",this.className),this.className=this.className||e===!1?"":x._data(this,"__className__")||"")})},hasClass:function(e){var t=" "+e+" ",n=0,r=this.length;for(;r>n;n++)if(1===this[n].nodeType&&(" "+this[n].className+" ").replace(U," ").indexOf(t)>=0)return!0;return!1},val:function(e){var n,r,i,o=this[0];{if(arguments.length)return i=x.isFunction(e),this.each(function(n){var o;1===this.nodeType&&(o=i?e.call(this,n,x(this).val()):e,null==o?o="":"number"==typeof o?o+="":x.isArray(o)&&(o=x.map(o,function(e){return null==e?"":e+""})),r=x.valHooks[this.type]||x.valHooks[this.nodeName.toLowerCase()],r&&"set"in r&&r.set(this,o,"value")!==t||(this.value=o))});if(o)return r=x.valHooks[o.type]||x.valHooks[o.nodeName.toLowerCase()],r&&"get"in r&&(n=r.get(o,"value"))!==t?n:(n=o.value,"string"==typeof n?n.replace(V,""):null==n?"":n)}}}),x.extend({valHooks:{option:{get:function(e){var t=x.find.attr(e,"value");return null!=t?t:e.text}},select:{get:function(e){var t,n,r=e.options,i=e.selectedIndex,o="select-one"===e.type||0>i,a=o?null:[],s=o?i+1:r.length,l=0>i?s:o?i:0;for(;s>l;l++)if(n=r[l],!(!n.selected&&l!==i||(x.support.optDisabled?n.disabled:null!==n.getAttribute("disabled"))||n.parentNode.disabled&&x.nodeName(n.parentNode,"optgroup"))){if(t=x(n).val(),o)return t;a.push(t)}return a},set:function(e,t){var n,r,i=e.options,o=x.makeArray(t),a=i.length;while(a--)r=i[a],(r.selected=x.inArray(x(r).val(),o)>=0)&&(n=!0);return n||(e.selectedIndex=-1),o}}},attr:function(e,n,r){var o,a,s=e.nodeType;if(e&&3!==s&&8!==s&&2!==s)return typeof e.getAttribute===i?x.prop(e,n,r):(1===s&&x.isXMLDoc(e)||(n=n.toLowerCase(),o=x.attrHooks[n]||(x.expr.match.bool.test(n)?X:z)),r===t?o&&"get"in o&&null!==(a=o.get(e,n))?a:(a=x.find.attr(e,n),null==a?t:a):null!==r?o&&"set"in o&&(a=o.set(e,r,n))!==t?a:(e.setAttribute(n,r+""),r):(x.removeAttr(e,n),t))},removeAttr:function(e,t){var n,r,i=0,o=t&&t.match(T);if(o&&1===e.nodeType)while(n=o[i++])r=x.propFix[n]||n,x.expr.match.bool.test(n)?K&&Q||!G.test(n)?e[r]=!1:e[x.camelCase("default-"+n)]=e[r]=!1:x.attr(e,n,""),e.removeAttribute(Q?n:r)},attrHooks:{type:{set:function(e,t){if(!x.support.radioValue&&"radio"===t&&x.nodeName(e,"input")){var n=e.value;return e.setAttribute("type",t),n&&(e.value=n),t}}}},propFix:{"for":"htmlFor","class":"className"},prop:function(e,n,r){var i,o,a,s=e.nodeType;if(e&&3!==s&&8!==s&&2!==s)return a=1!==s||!x.isXMLDoc(e),a&&(n=x.propFix[n]||n,o=x.propHooks[n]),r!==t?o&&"set"in o&&(i=o.set(e,r,n))!==t?i:e[n]=r:o&&"get"in o&&null!==(i=o.get(e,n))?i:e[n]},propHooks:{tabIndex:{get:function(e){var t=x.find.attr(e,"tabindex");return t?parseInt(t,10):Y.test(e.nodeName)||J.test(e.nodeName)&&e.href?0:-1}}}}),X={set:function(e,t,n){return t===!1?x.removeAttr(e,n):K&&Q||!G.test(n)?e.setAttribute(!Q&&x.propFix[n]||n,n):e[x.camelCase("default-"+n)]=e[n]=!0,n}},x.each(x.expr.match.bool.source.match(/\w+/g),function(e,n){var r=x.expr.attrHandle[n]||x.find.attr;x.expr.attrHandle[n]=K&&Q||!G.test(n)?function(e,n,i){var o=x.expr.attrHandle[n],a=i?t:(x.expr.attrHandle[n]=t)!=r(e,n,i)?n.toLowerCase():null;return x.expr.attrHandle[n]=o,a}:function(e,n,r){return r?t:e[x.camelCase("default-"+n)]?n.toLowerCase():null}}),K&&Q||(x.attrHooks.value={set:function(e,n,r){return x.nodeName(e,"input")?(e.defaultValue=n,t):z&&z.set(e,n,r)}}),Q||(z={set:function(e,n,r){var i=e.getAttributeNode(r);return i||e.setAttributeNode(i=e.ownerDocument.createAttribute(r)),i.value=n+="","value"===r||n===e.getAttribute(r)?n:t}},x.expr.attrHandle.id=x.expr.attrHandle.name=x.expr.attrHandle.coords=function(e,n,r){var i;return r?t:(i=e.getAttributeNode(n))&&""!==i.value?i.value:null},x.valHooks.button={get:function(e,n){var r=e.getAttributeNode(n);return r&&r.specified?r.value:t},set:z.set},x.attrHooks.contenteditable={set:function(e,t,n){z.set(e,""===t?!1:t,n)}},x.each(["width","height"],function(e,n){x.attrHooks[n]={set:function(e,r){return""===r?(e.setAttribute(n,"auto"),r):t}}})),x.support.hrefNormalized||x.each(["href","src"],function(e,t){x.propHooks[t]={get:function(e){return e.getAttribute(t,4)}}}),x.support.style||(x.attrHooks.style={get:function(e){return e.style.cssText||t},set:function(e,t){return e.style.cssText=t+""}}),x.support.optSelected||(x.propHooks.selected={get:function(e){var t=e.parentNode;return t&&(t.selectedIndex,t.parentNode&&t.parentNode.selectedIndex),null}}),x.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],function(){x.propFix[this.toLowerCase()]=this}),x.support.enctype||(x.propFix.enctype="encoding"),x.each(["radio","checkbox"],function(){x.valHooks[this]={set:function(e,n){return x.isArray(n)?e.checked=x.inArray(x(e).val(),n)>=0:t}},x.support.checkOn||(x.valHooks[this].get=function(e){return null===e.getAttribute("value")?"on":e.value})});var Z=/^(?:input|select|textarea)$/i,et=/^key/,tt=/^(?:mouse|contextmenu)|click/,nt=/^(?:focusinfocus|focusoutblur)$/,rt=/^([^.]*)(?:\.(.+)|)$/;function it(){return!0}function ot(){return!1}function at(){try{return a.activeElement}catch(e){}}x.event={global:{},add:function(e,n,r,o,a){var s,l,u,c,p,f,d,h,g,m,y,v=x._data(e);if(v){r.handler&&(c=r,r=c.handler,a=c.selector),r.guid||(r.guid=x.guid++),(l=v.events)||(l=v.events={}),(f=v.handle)||(f=v.handle=function(e){return typeof x===i||e&&x.event.triggered===e.type?t:x.event.dispatch.apply(f.elem,arguments)},f.elem=e),n=(n||"").match(T)||[""],u=n.length;while(u--)s=rt.exec(n[u])||[],g=y=s[1],m=(s[2]||"").split(".").sort(),g&&(p=x.event.special[g]||{},g=(a?p.delegateType:p.bindType)||g,p=x.event.special[g]||{},d=x.extend({type:g,origType:y,data:o,handler:r,guid:r.guid,selector:a,needsContext:a&&x.expr.match.needsContext.test(a),namespace:m.join(".")},c),(h=l[g])||(h=l[g]=[],h.delegateCount=0,p.setup&&p.setup.call(e,o,m,f)!==!1||(e.addEventListener?e.addEventListener(g,f,!1):e.attachEvent&&e.attachEvent("on"+g,f))),p.add&&(p.add.call(e,d),d.handler.guid||(d.handler.guid=r.guid)),a?h.splice(h.delegateCount++,0,d):h.push(d),x.event.global[g]=!0);e=null}},remove:function(e,t,n,r,i){var o,a,s,l,u,c,p,f,d,h,g,m=x.hasData(e)&&x._data(e);if(m&&(c=m.events)){t=(t||"").match(T)||[""],u=t.length;while(u--)if(s=rt.exec(t[u])||[],d=g=s[1],h=(s[2]||"").split(".").sort(),d){p=x.event.special[d]||{},d=(r?p.delegateType:p.bindType)||d,f=c[d]||[],s=s[2]&&RegExp("(^|\\.)"+h.join("\\.(?:.*\\.|)")+"(\\.|$)"),l=o=f.length;while(o--)a=f[o],!i&&g!==a.origType||n&&n.guid!==a.guid||s&&!s.test(a.namespace)||r&&r!==a.selector&&("**"!==r||!a.selector)||(f.splice(o,1),a.selector&&f.delegateCount--,p.remove&&p.remove.call(e,a));l&&!f.length&&(p.teardown&&p.teardown.call(e,h,m.handle)!==!1||x.removeEvent(e,d,m.handle),delete c[d])}else for(d in c)x.event.remove(e,d+t[u],n,r,!0);x.isEmptyObject(c)&&(delete m.handle,x._removeData(e,"events"))}},trigger:function(n,r,i,o){var s,l,u,c,p,f,d,h=[i||a],g=v.call(n,"type")?n.type:n,m=v.call(n,"namespace")?n.namespace.split("."):[];if(u=f=i=i||a,3!==i.nodeType&&8!==i.nodeType&&!nt.test(g+x.event.triggered)&&(g.indexOf(".")>=0&&(m=g.split("."),g=m.shift(),m.sort()),l=0>g.indexOf(":")&&"on"+g,n=n[x.expando]?n:new x.Event(g,"object"==typeof n&&n),n.isTrigger=o?2:3,n.namespace=m.join("."),n.namespace_re=n.namespace?RegExp("(^|\\.)"+m.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,n.result=t,n.target||(n.target=i),r=null==r?[n]:x.makeArray(r,[n]),p=x.event.special[g]||{},o||!p.trigger||p.trigger.apply(i,r)!==!1)){if(!o&&!p.noBubble&&!x.isWindow(i)){for(c=p.delegateType||g,nt.test(c+g)||(u=u.parentNode);u;u=u.parentNode)h.push(u),f=u;f===(i.ownerDocument||a)&&h.push(f.defaultView||f.parentWindow||e)}d=0;while((u=h[d++])&&!n.isPropagationStopped())n.type=d>1?c:p.bindType||g,s=(x._data(u,"events")||{})[n.type]&&x._data(u,"handle"),s&&s.apply(u,r),s=l&&u[l],s&&x.acceptData(u)&&s.apply&&s.apply(u,r)===!1&&n.preventDefault();if(n.type=g,!o&&!n.isDefaultPrevented()&&(!p._default||p._default.apply(h.pop(),r)===!1)&&x.acceptData(i)&&l&&i[g]&&!x.isWindow(i)){f=i[l],f&&(i[l]=null),x.event.triggered=g;try{i[g]()}catch(y){}x.event.triggered=t,f&&(i[l]=f)}return n.result}},dispatch:function(e){e=x.event.fix(e);var n,r,i,o,a,s=[],l=g.call(arguments),u=(x._data(this,"events")||{})[e.type]||[],c=x.event.special[e.type]||{};if(l[0]=e,e.delegateTarget=this,!c.preDispatch||c.preDispatch.call(this,e)!==!1){s=x.event.handlers.call(this,e,u),n=0;while((o=s[n++])&&!e.isPropagationStopped()){e.currentTarget=o.elem,a=0;while((i=o.handlers[a++])&&!e.isImmediatePropagationStopped())(!e.namespace_re||e.namespace_re.test(i.namespace))&&(e.handleObj=i,e.data=i.data,r=((x.event.special[i.origType]||{}).handle||i.handler).apply(o.elem,l),r!==t&&(e.result=r)===!1&&(e.preventDefault(),e.stopPropagation()))}return c.postDispatch&&c.postDispatch.call(this,e),e.result}},handlers:function(e,n){var r,i,o,a,s=[],l=n.delegateCount,u=e.target;if(l&&u.nodeType&&(!e.button||"click"!==e.type))for(;u!=this;u=u.parentNode||this)if(1===u.nodeType&&(u.disabled!==!0||"click"!==e.type)){for(o=[],a=0;l>a;a++)i=n[a],r=i.selector+" ",o[r]===t&&(o[r]=i.needsContext?x(r,this).index(u)>=0:x.find(r,this,null,[u]).length),o[r]&&o.push(i);o.length&&s.push({elem:u,handlers:o})}return n.length>l&&s.push({elem:this,handlers:n.slice(l)}),s},fix:function(e){if(e[x.expando])return e;var t,n,r,i=e.type,o=e,s=this.fixHooks[i];s||(this.fixHooks[i]=s=tt.test(i)?this.mouseHooks:et.test(i)?this.keyHooks:{}),r=s.props?this.props.concat(s.props):this.props,e=new x.Event(o),t=r.length;while(t--)n=r[t],e[n]=o[n];return e.target||(e.target=o.srcElement||a),3===e.target.nodeType&&(e.target=e.target.parentNode),e.metaKey=!!e.metaKey,s.filter?s.filter(e,o):e},props:"altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "),fixHooks:{},keyHooks:{props:"char charCode key keyCode".split(" "),filter:function(e,t){return null==e.which&&(e.which=null!=t.charCode?t.charCode:t.keyCode),e}},mouseHooks:{props:"button buttons clientX clientY fromElement offsetX offsetY pageX pageY screenX screenY toElement".split(" "),filter:function(e,n){var r,i,o,s=n.button,l=n.fromElement;return null==e.pageX&&null!=n.clientX&&(i=e.target.ownerDocument||a,o=i.documentElement,r=i.body,e.pageX=n.clientX+(o&&o.scrollLeft||r&&r.scrollLeft||0)-(o&&o.clientLeft||r&&r.clientLeft||0),e.pageY=n.clientY+(o&&o.scrollTop||r&&r.scrollTop||0)-(o&&o.clientTop||r&&r.clientTop||0)),!e.relatedTarget&&l&&(e.relatedTarget=l===e.target?n.toElement:l),e.which||s===t||(e.which=1&s?1:2&s?3:4&s?2:0),e}},special:{load:{noBubble:!0},focus:{trigger:function(){if(this!==at()&&this.focus)try{return this.focus(),!1}catch(e){}},delegateType:"focusin"},blur:{trigger:function(){return this===at()&&this.blur?(this.blur(),!1):t},delegateType:"focusout"},click:{trigger:function(){return x.nodeName(this,"input")&&"checkbox"===this.type&&this.click?(this.click(),!1):t},_default:function(e){return x.nodeName(e.target,"a")}},beforeunload:{postDispatch:function(e){e.result!==t&&(e.originalEvent.returnValue=e.result)}}},simulate:function(e,t,n,r){var i=x.extend(new x.Event,n,{type:e,isSimulated:!0,originalEvent:{}});r?x.event.trigger(i,null,t):x.event.dispatch.call(t,i),i.isDefaultPrevented()&&n.preventDefault()}},x.removeEvent=a.removeEventListener?function(e,t,n){e.removeEventListener&&e.removeEventListener(t,n,!1)}:function(e,t,n){var r="on"+t;e.detachEvent&&(typeof e[r]===i&&(e[r]=null),e.detachEvent(r,n))},x.Event=function(e,n){return this instanceof x.Event?(e&&e.type?(this.originalEvent=e,this.type=e.type,this.isDefaultPrevented=e.defaultPrevented||e.returnValue===!1||e.getPreventDefault&&e.getPreventDefault()?it:ot):this.type=e,n&&x.extend(this,n),this.timeStamp=e&&e.timeStamp||x.now(),this[x.expando]=!0,t):new x.Event(e,n)},x.Event.prototype={isDefaultPrevented:ot,isPropagationStopped:ot,isImmediatePropagationStopped:ot,preventDefault:function(){var e=this.originalEvent;this.isDefaultPrevented=it,e&&(e.preventDefault?e.preventDefault():e.returnValue=!1)},stopPropagation:function(){var e=this.originalEvent;this.isPropagationStopped=it,e&&(e.stopPropagation&&e.stopPropagation(),e.cancelBubble=!0)},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=it,this.stopPropagation()}},x.each({mouseenter:"mouseover",mouseleave:"mouseout"},function(e,t){x.event.special[e]={delegateType:t,bindType:t,handle:function(e){var n,r=this,i=e.relatedTarget,o=e.handleObj;return(!i||i!==r&&!x.contains(r,i))&&(e.type=o.origType,n=o.handler.apply(this,arguments),e.type=t),n}}}),x.support.submitBubbles||(x.event.special.submit={setup:function(){return x.nodeName(this,"form")?!1:(x.event.add(this,"click._submit keypress._submit",function(e){var n=e.target,r=x.nodeName(n,"input")||x.nodeName(n,"button")?n.form:t;r&&!x._data(r,"submitBubbles")&&(x.event.add(r,"submit._submit",function(e){e._submit_bubble=!0}),x._data(r,"submitBubbles",!0))}),t)},postDispatch:function(e){e._submit_bubble&&(delete e._submit_bubble,this.parentNode&&!e.isTrigger&&x.event.simulate("submit",this.parentNode,e,!0))},teardown:function(){return x.nodeName(this,"form")?!1:(x.event.remove(this,"._submit"),t)}}),x.support.changeBubbles||(x.event.special.change={setup:function(){return Z.test(this.nodeName)?(("checkbox"===this.type||"radio"===this.type)&&(x.event.add(this,"propertychange._change",function(e){"checked"===e.originalEvent.propertyName&&(this._just_changed=!0)}),x.event.add(this,"click._change",function(e){this._just_changed&&!e.isTrigger&&(this._just_changed=!1),x.event.simulate("change",this,e,!0)})),!1):(x.event.add(this,"beforeactivate._change",function(e){var t=e.target;Z.test(t.nodeName)&&!x._data(t,"changeBubbles")&&(x.event.add(t,"change._change",function(e){!this.parentNode||e.isSimulated||e.isTrigger||x.event.simulate("change",this.parentNode,e,!0)}),x._data(t,"changeBubbles",!0))}),t)},handle:function(e){var n=e.target;return this!==n||e.isSimulated||e.isTrigger||"radio"!==n.type&&"checkbox"!==n.type?e.handleObj.handler.apply(this,arguments):t},teardown:function(){return x.event.remove(this,"._change"),!Z.test(this.nodeName)}}),x.support.focusinBubbles||x.each({focus:"focusin",blur:"focusout"},function(e,t){var n=0,r=function(e){x.event.simulate(t,e.target,x.event.fix(e),!0)};x.event.special[t]={setup:function(){0===n++&&a.addEventListener(e,r,!0)},teardown:function(){0===--n&&a.removeEventListener(e,r,!0)}}}),x.fn.extend({on:function(e,n,r,i,o){var a,s;if("object"==typeof e){"string"!=typeof n&&(r=r||n,n=t);for(a in e)this.on(a,n,r,e[a],o);return this}if(null==r&&null==i?(i=n,r=n=t):null==i&&("string"==typeof n?(i=r,r=t):(i=r,r=n,n=t)),i===!1)i=ot;else if(!i)return this;return 1===o&&(s=i,i=function(e){return x().off(e),s.apply(this,arguments)},i.guid=s.guid||(s.guid=x.guid++)),this.each(function(){x.event.add(this,e,i,r,n)})},one:function(e,t,n,r){return this.on(e,t,n,r,1)},off:function(e,n,r){var i,o;if(e&&e.preventDefault&&e.handleObj)return i=e.handleObj,x(e.delegateTarget).off(i.namespace?i.origType+"."+i.namespace:i.origType,i.selector,i.handler),this;if("object"==typeof e){for(o in e)this.off(o,n,e[o]);return this}return(n===!1||"function"==typeof n)&&(r=n,n=t),r===!1&&(r=ot),this.each(function(){x.event.remove(this,e,r,n)})},trigger:function(e,t){return this.each(function(){x.event.trigger(e,t,this)})},triggerHandler:function(e,n){var r=this[0];return r?x.event.trigger(e,n,r,!0):t}});var st=/^.[^:#\[\.,]*$/,lt=/^(?:parents|prev(?:Until|All))/,ut=x.expr.match.needsContext,ct={children:!0,contents:!0,next:!0,prev:!0};x.fn.extend({find:function(e){var t,n=[],r=this,i=r.length;if("string"!=typeof e)return this.pushStack(x(e).filter(function(){for(t=0;i>t;t++)if(x.contains(r[t],this))return!0}));for(t=0;i>t;t++)x.find(e,r[t],n);return n=this.pushStack(i>1?x.unique(n):n),n.selector=this.selector?this.selector+" "+e:e,n},has:function(e){var t,n=x(e,this),r=n.length;return this.filter(function(){for(t=0;r>t;t++)if(x.contains(this,n[t]))return!0})},not:function(e){return this.pushStack(ft(this,e||[],!0))},filter:function(e){return this.pushStack(ft(this,e||[],!1))},is:function(e){return!!ft(this,"string"==typeof e&&ut.test(e)?x(e):e||[],!1).length},closest:function(e,t){var n,r=0,i=this.length,o=[],a=ut.test(e)||"string"!=typeof e?x(e,t||this.context):0;for(;i>r;r++)for(n=this[r];n&&n!==t;n=n.parentNode)if(11>n.nodeType&&(a?a.index(n)>-1:1===n.nodeType&&x.find.matchesSelector(n,e))){n=o.push(n);break}return this.pushStack(o.length>1?x.unique(o):o)},index:function(e){return e?"string"==typeof e?x.inArray(this[0],x(e)):x.inArray(e.jquery?e[0]:e,this):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(e,t){var n="string"==typeof e?x(e,t):x.makeArray(e&&e.nodeType?[e]:e),r=x.merge(this.get(),n);return this.pushStack(x.unique(r))},addBack:function(e){return this.add(null==e?this.prevObject:this.prevObject.filter(e))}});function pt(e,t){do e=e[t];while(e&&1!==e.nodeType);return e}x.each({parent:function(e){var t=e.parentNode;return t&&11!==t.nodeType?t:null},parents:function(e){return x.dir(e,"parentNode")},parentsUntil:function(e,t,n){return x.dir(e,"parentNode",n)},next:function(e){return pt(e,"nextSibling")},prev:function(e){return pt(e,"previousSibling")},nextAll:function(e){return x.dir(e,"nextSibling")},prevAll:function(e){return x.dir(e,"previousSibling")},nextUntil:function(e,t,n){return x.dir(e,"nextSibling",n)},prevUntil:function(e,t,n){return x.dir(e,"previousSibling",n)},siblings:function(e){return x.sibling((e.parentNode||{}).firstChild,e)},children:function(e){return x.sibling(e.firstChild)},contents:function(e){return x.nodeName(e,"iframe")?e.contentDocument||e.contentWindow.document:x.merge([],e.childNodes)}},function(e,t){x.fn[e]=function(n,r){var i=x.map(this,t,n);return"Until"!==e.slice(-5)&&(r=n),r&&"string"==typeof r&&(i=x.filter(r,i)),this.length>1&&(ct[e]||(i=x.unique(i)),lt.test(e)&&(i=i.reverse())),this.pushStack(i)}}),x.extend({filter:function(e,t,n){var r=t[0];return n&&(e=":not("+e+")"),1===t.length&&1===r.nodeType?x.find.matchesSelector(r,e)?[r]:[]:x.find.matches(e,x.grep(t,function(e){return 1===e.nodeType}))},dir:function(e,n,r){var i=[],o=e[n];while(o&&9!==o.nodeType&&(r===t||1!==o.nodeType||!x(o).is(r)))1===o.nodeType&&i.push(o),o=o[n];return i},sibling:function(e,t){var n=[];for(;e;e=e.nextSibling)1===e.nodeType&&e!==t&&n.push(e);return n}});function ft(e,t,n){if(x.isFunction(t))return x.grep(e,function(e,r){return!!t.call(e,r,e)!==n});if(t.nodeType)return x.grep(e,function(e){return e===t!==n});if("string"==typeof t){if(st.test(t))return x.filter(t,e,n);t=x.filter(t,e)}return x.grep(e,function(e){return x.inArray(e,t)>=0!==n})}function dt(e){var t=ht.split("|"),n=e.createDocumentFragment();if(n.createElement)while(t.length)n.createElement(t.pop());return n}var ht="abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|header|hgroup|mark|meter|nav|output|progress|section|summary|time|video",gt=/ jQuery\d+="(?:null|\d+)"/g,mt=RegExp("<(?:"+ht+")[\\s/>]","i"),yt=/^\s+/,vt=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,bt=/<([\w:]+)/,xt=/\s*$/g,At={option:[1,""],legend:[1,"
    ","
    "],area:[1,"",""],param:[1,"",""],thead:[1,"","
    "],tr:[2,"","
    "],col:[2,"","
    "],td:[3,"","
    "],_default:x.support.htmlSerialize?[0,"",""]:[1,"X
    ","
    "]},jt=dt(a),Dt=jt.appendChild(a.createElement("div"));At.optgroup=At.option,At.tbody=At.tfoot=At.colgroup=At.caption=At.thead,At.th=At.td,x.fn.extend({text:function(e){return x.access(this,function(e){return e===t?x.text(this):this.empty().append((this[0]&&this[0].ownerDocument||a).createTextNode(e))},null,e,arguments.length)},append:function(){return this.domManip(arguments,function(e){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var t=Lt(this,e);t.appendChild(e)}})},prepend:function(){return this.domManip(arguments,function(e){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var t=Lt(this,e);t.insertBefore(e,t.firstChild)}})},before:function(){return this.domManip(arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this)})},after:function(){return this.domManip(arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this.nextSibling)})},remove:function(e,t){var n,r=e?x.filter(e,this):this,i=0;for(;null!=(n=r[i]);i++)t||1!==n.nodeType||x.cleanData(Ft(n)),n.parentNode&&(t&&x.contains(n.ownerDocument,n)&&_t(Ft(n,"script")),n.parentNode.removeChild(n));return this},empty:function(){var e,t=0;for(;null!=(e=this[t]);t++){1===e.nodeType&&x.cleanData(Ft(e,!1));while(e.firstChild)e.removeChild(e.firstChild);e.options&&x.nodeName(e,"select")&&(e.options.length=0)}return this},clone:function(e,t){return e=null==e?!1:e,t=null==t?e:t,this.map(function(){return x.clone(this,e,t)})},html:function(e){return x.access(this,function(e){var n=this[0]||{},r=0,i=this.length;if(e===t)return 1===n.nodeType?n.innerHTML.replace(gt,""):t;if(!("string"!=typeof e||Tt.test(e)||!x.support.htmlSerialize&&mt.test(e)||!x.support.leadingWhitespace&&yt.test(e)||At[(bt.exec(e)||["",""])[1].toLowerCase()])){e=e.replace(vt,"<$1>");try{for(;i>r;r++)n=this[r]||{},1===n.nodeType&&(x.cleanData(Ft(n,!1)),n.innerHTML=e);n=0}catch(o){}}n&&this.empty().append(e)},null,e,arguments.length)},replaceWith:function(){var e=x.map(this,function(e){return[e.nextSibling,e.parentNode]}),t=0;return this.domManip(arguments,function(n){var r=e[t++],i=e[t++];i&&(r&&r.parentNode!==i&&(r=this.nextSibling),x(this).remove(),i.insertBefore(n,r))},!0),t?this:this.remove()},detach:function(e){return this.remove(e,!0)},domManip:function(e,t,n){e=d.apply([],e);var r,i,o,a,s,l,u=0,c=this.length,p=this,f=c-1,h=e[0],g=x.isFunction(h);if(g||!(1>=c||"string"!=typeof h||x.support.checkClone)&&Nt.test(h))return this.each(function(r){var i=p.eq(r);g&&(e[0]=h.call(this,r,i.html())),i.domManip(e,t,n)});if(c&&(l=x.buildFragment(e,this[0].ownerDocument,!1,!n&&this),r=l.firstChild,1===l.childNodes.length&&(l=r),r)){for(a=x.map(Ft(l,"script"),Ht),o=a.length;c>u;u++)i=l,u!==f&&(i=x.clone(i,!0,!0),o&&x.merge(a,Ft(i,"script"))),t.call(this[u],i,u);if(o)for(s=a[a.length-1].ownerDocument,x.map(a,qt),u=0;o>u;u++)i=a[u],kt.test(i.type||"")&&!x._data(i,"globalEval")&&x.contains(s,i)&&(i.src?x._evalUrl(i.src):x.globalEval((i.text||i.textContent||i.innerHTML||"").replace(St,"")));l=r=null}return this}});function Lt(e,t){return x.nodeName(e,"table")&&x.nodeName(1===t.nodeType?t:t.firstChild,"tr")?e.getElementsByTagName("tbody")[0]||e.appendChild(e.ownerDocument.createElement("tbody")):e}function Ht(e){return e.type=(null!==x.find.attr(e,"type"))+"/"+e.type,e}function qt(e){var t=Et.exec(e.type);return t?e.type=t[1]:e.removeAttribute("type"),e}function _t(e,t){var n,r=0;for(;null!=(n=e[r]);r++)x._data(n,"globalEval",!t||x._data(t[r],"globalEval"))}function Mt(e,t){if(1===t.nodeType&&x.hasData(e)){var n,r,i,o=x._data(e),a=x._data(t,o),s=o.events;if(s){delete a.handle,a.events={};for(n in s)for(r=0,i=s[n].length;i>r;r++)x.event.add(t,n,s[n][r])}a.data&&(a.data=x.extend({},a.data))}}function Ot(e,t){var n,r,i;if(1===t.nodeType){if(n=t.nodeName.toLowerCase(),!x.support.noCloneEvent&&t[x.expando]){i=x._data(t);for(r in i.events)x.removeEvent(t,r,i.handle);t.removeAttribute(x.expando)}"script"===n&&t.text!==e.text?(Ht(t).text=e.text,qt(t)):"object"===n?(t.parentNode&&(t.outerHTML=e.outerHTML),x.support.html5Clone&&e.innerHTML&&!x.trim(t.innerHTML)&&(t.innerHTML=e.innerHTML)):"input"===n&&Ct.test(e.type)?(t.defaultChecked=t.checked=e.checked,t.value!==e.value&&(t.value=e.value)):"option"===n?t.defaultSelected=t.selected=e.defaultSelected:("input"===n||"textarea"===n)&&(t.defaultValue=e.defaultValue)}}x.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(e,t){x.fn[e]=function(e){var n,r=0,i=[],o=x(e),a=o.length-1;for(;a>=r;r++)n=r===a?this:this.clone(!0),x(o[r])[t](n),h.apply(i,n.get());return this.pushStack(i)}});function Ft(e,n){var r,o,a=0,s=typeof e.getElementsByTagName!==i?e.getElementsByTagName(n||"*"):typeof e.querySelectorAll!==i?e.querySelectorAll(n||"*"):t;if(!s)for(s=[],r=e.childNodes||e;null!=(o=r[a]);a++)!n||x.nodeName(o,n)?s.push(o):x.merge(s,Ft(o,n));return n===t||n&&x.nodeName(e,n)?x.merge([e],s):s}function Bt(e){Ct.test(e.type)&&(e.defaultChecked=e.checked)}x.extend({clone:function(e,t,n){var r,i,o,a,s,l=x.contains(e.ownerDocument,e);if(x.support.html5Clone||x.isXMLDoc(e)||!mt.test("<"+e.nodeName+">")?o=e.cloneNode(!0):(Dt.innerHTML=e.outerHTML,Dt.removeChild(o=Dt.firstChild)),!(x.support.noCloneEvent&&x.support.noCloneChecked||1!==e.nodeType&&11!==e.nodeType||x.isXMLDoc(e)))for(r=Ft(o),s=Ft(e),a=0;null!=(i=s[a]);++a)r[a]&&Ot(i,r[a]);if(t)if(n)for(s=s||Ft(e),r=r||Ft(o),a=0;null!=(i=s[a]);a++)Mt(i,r[a]);else Mt(e,o);return r=Ft(o,"script"),r.length>0&&_t(r,!l&&Ft(e,"script")),r=s=i=null,o},buildFragment:function(e,t,n,r){var i,o,a,s,l,u,c,p=e.length,f=dt(t),d=[],h=0;for(;p>h;h++)if(o=e[h],o||0===o)if("object"===x.type(o))x.merge(d,o.nodeType?[o]:o);else if(wt.test(o)){s=s||f.appendChild(t.createElement("div")),l=(bt.exec(o)||["",""])[1].toLowerCase(),c=At[l]||At._default,s.innerHTML=c[1]+o.replace(vt,"<$1>")+c[2],i=c[0];while(i--)s=s.lastChild;if(!x.support.leadingWhitespace&&yt.test(o)&&d.push(t.createTextNode(yt.exec(o)[0])),!x.support.tbody){o="table"!==l||xt.test(o)?""!==c[1]||xt.test(o)?0:s:s.firstChild,i=o&&o.childNodes.length;while(i--)x.nodeName(u=o.childNodes[i],"tbody")&&!u.childNodes.length&&o.removeChild(u)}x.merge(d,s.childNodes),s.textContent="";while(s.firstChild)s.removeChild(s.firstChild);s=f.lastChild}else d.push(t.createTextNode(o));s&&f.removeChild(s),x.support.appendChecked||x.grep(Ft(d,"input"),Bt),h=0;while(o=d[h++])if((!r||-1===x.inArray(o,r))&&(a=x.contains(o.ownerDocument,o),s=Ft(f.appendChild(o),"script"),a&&_t(s),n)){i=0;while(o=s[i++])kt.test(o.type||"")&&n.push(o)}return s=null,f},cleanData:function(e,t){var n,r,o,a,s=0,l=x.expando,u=x.cache,c=x.support.deleteExpando,f=x.event.special;for(;null!=(n=e[s]);s++)if((t||x.acceptData(n))&&(o=n[l],a=o&&u[o])){if(a.events)for(r in a.events)f[r]?x.event.remove(n,r):x.removeEvent(n,r,a.handle); +u[o]&&(delete u[o],c?delete n[l]:typeof n.removeAttribute!==i?n.removeAttribute(l):n[l]=null,p.push(o))}},_evalUrl:function(e){return x.ajax({url:e,type:"GET",dataType:"script",async:!1,global:!1,"throws":!0})}}),x.fn.extend({wrapAll:function(e){if(x.isFunction(e))return this.each(function(t){x(this).wrapAll(e.call(this,t))});if(this[0]){var t=x(e,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&t.insertBefore(this[0]),t.map(function(){var e=this;while(e.firstChild&&1===e.firstChild.nodeType)e=e.firstChild;return e}).append(this)}return this},wrapInner:function(e){return x.isFunction(e)?this.each(function(t){x(this).wrapInner(e.call(this,t))}):this.each(function(){var t=x(this),n=t.contents();n.length?n.wrapAll(e):t.append(e)})},wrap:function(e){var t=x.isFunction(e);return this.each(function(n){x(this).wrapAll(t?e.call(this,n):e)})},unwrap:function(){return this.parent().each(function(){x.nodeName(this,"body")||x(this).replaceWith(this.childNodes)}).end()}});var Pt,Rt,Wt,$t=/alpha\([^)]*\)/i,It=/opacity\s*=\s*([^)]*)/,zt=/^(top|right|bottom|left)$/,Xt=/^(none|table(?!-c[ea]).+)/,Ut=/^margin/,Vt=RegExp("^("+w+")(.*)$","i"),Yt=RegExp("^("+w+")(?!px)[a-z%]+$","i"),Jt=RegExp("^([+-])=("+w+")","i"),Gt={BODY:"block"},Qt={position:"absolute",visibility:"hidden",display:"block"},Kt={letterSpacing:0,fontWeight:400},Zt=["Top","Right","Bottom","Left"],en=["Webkit","O","Moz","ms"];function tn(e,t){if(t in e)return t;var n=t.charAt(0).toUpperCase()+t.slice(1),r=t,i=en.length;while(i--)if(t=en[i]+n,t in e)return t;return r}function nn(e,t){return e=t||e,"none"===x.css(e,"display")||!x.contains(e.ownerDocument,e)}function rn(e,t){var n,r,i,o=[],a=0,s=e.length;for(;s>a;a++)r=e[a],r.style&&(o[a]=x._data(r,"olddisplay"),n=r.style.display,t?(o[a]||"none"!==n||(r.style.display=""),""===r.style.display&&nn(r)&&(o[a]=x._data(r,"olddisplay",ln(r.nodeName)))):o[a]||(i=nn(r),(n&&"none"!==n||!i)&&x._data(r,"olddisplay",i?n:x.css(r,"display"))));for(a=0;s>a;a++)r=e[a],r.style&&(t&&"none"!==r.style.display&&""!==r.style.display||(r.style.display=t?o[a]||"":"none"));return e}x.fn.extend({css:function(e,n){return x.access(this,function(e,n,r){var i,o,a={},s=0;if(x.isArray(n)){for(o=Rt(e),i=n.length;i>s;s++)a[n[s]]=x.css(e,n[s],!1,o);return a}return r!==t?x.style(e,n,r):x.css(e,n)},e,n,arguments.length>1)},show:function(){return rn(this,!0)},hide:function(){return rn(this)},toggle:function(e){return"boolean"==typeof e?e?this.show():this.hide():this.each(function(){nn(this)?x(this).show():x(this).hide()})}}),x.extend({cssHooks:{opacity:{get:function(e,t){if(t){var n=Wt(e,"opacity");return""===n?"1":n}}}},cssNumber:{columnCount:!0,fillOpacity:!0,fontWeight:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":x.support.cssFloat?"cssFloat":"styleFloat"},style:function(e,n,r,i){if(e&&3!==e.nodeType&&8!==e.nodeType&&e.style){var o,a,s,l=x.camelCase(n),u=e.style;if(n=x.cssProps[l]||(x.cssProps[l]=tn(u,l)),s=x.cssHooks[n]||x.cssHooks[l],r===t)return s&&"get"in s&&(o=s.get(e,!1,i))!==t?o:u[n];if(a=typeof r,"string"===a&&(o=Jt.exec(r))&&(r=(o[1]+1)*o[2]+parseFloat(x.css(e,n)),a="number"),!(null==r||"number"===a&&isNaN(r)||("number"!==a||x.cssNumber[l]||(r+="px"),x.support.clearCloneStyle||""!==r||0!==n.indexOf("background")||(u[n]="inherit"),s&&"set"in s&&(r=s.set(e,r,i))===t)))try{u[n]=r}catch(c){}}},css:function(e,n,r,i){var o,a,s,l=x.camelCase(n);return n=x.cssProps[l]||(x.cssProps[l]=tn(e.style,l)),s=x.cssHooks[n]||x.cssHooks[l],s&&"get"in s&&(a=s.get(e,!0,r)),a===t&&(a=Wt(e,n,i)),"normal"===a&&n in Kt&&(a=Kt[n]),""===r||r?(o=parseFloat(a),r===!0||x.isNumeric(o)?o||0:a):a}}),e.getComputedStyle?(Rt=function(t){return e.getComputedStyle(t,null)},Wt=function(e,n,r){var i,o,a,s=r||Rt(e),l=s?s.getPropertyValue(n)||s[n]:t,u=e.style;return s&&(""!==l||x.contains(e.ownerDocument,e)||(l=x.style(e,n)),Yt.test(l)&&Ut.test(n)&&(i=u.width,o=u.minWidth,a=u.maxWidth,u.minWidth=u.maxWidth=u.width=l,l=s.width,u.width=i,u.minWidth=o,u.maxWidth=a)),l}):a.documentElement.currentStyle&&(Rt=function(e){return e.currentStyle},Wt=function(e,n,r){var i,o,a,s=r||Rt(e),l=s?s[n]:t,u=e.style;return null==l&&u&&u[n]&&(l=u[n]),Yt.test(l)&&!zt.test(n)&&(i=u.left,o=e.runtimeStyle,a=o&&o.left,a&&(o.left=e.currentStyle.left),u.left="fontSize"===n?"1em":l,l=u.pixelLeft+"px",u.left=i,a&&(o.left=a)),""===l?"auto":l});function on(e,t,n){var r=Vt.exec(t);return r?Math.max(0,r[1]-(n||0))+(r[2]||"px"):t}function an(e,t,n,r,i){var o=n===(r?"border":"content")?4:"width"===t?1:0,a=0;for(;4>o;o+=2)"margin"===n&&(a+=x.css(e,n+Zt[o],!0,i)),r?("content"===n&&(a-=x.css(e,"padding"+Zt[o],!0,i)),"margin"!==n&&(a-=x.css(e,"border"+Zt[o]+"Width",!0,i))):(a+=x.css(e,"padding"+Zt[o],!0,i),"padding"!==n&&(a+=x.css(e,"border"+Zt[o]+"Width",!0,i)));return a}function sn(e,t,n){var r=!0,i="width"===t?e.offsetWidth:e.offsetHeight,o=Rt(e),a=x.support.boxSizing&&"border-box"===x.css(e,"boxSizing",!1,o);if(0>=i||null==i){if(i=Wt(e,t,o),(0>i||null==i)&&(i=e.style[t]),Yt.test(i))return i;r=a&&(x.support.boxSizingReliable||i===e.style[t]),i=parseFloat(i)||0}return i+an(e,t,n||(a?"border":"content"),r,o)+"px"}function ln(e){var t=a,n=Gt[e];return n||(n=un(e,t),"none"!==n&&n||(Pt=(Pt||x("