diff --git a/CHANGELOG.md b/CHANGELOG.md index 89e1b3f..26a34e3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## Features +- Add `--ssl` flag to `sls wsgi serve` (#103) - Add log message when skipping handler on warmup events (#95) - Add options for disabling threading and setting number of processes when invoking `sls wsgi serve` (#100) @@ -11,14 +12,13 @@ _Paul Bowsher_ -- Fix Flask CLI invocation of built-in commands (#99) - - _Mischa Spiegelmock_ - ## Bugs - Properly decode event `path` into environ `PATH_INFO` (#93) - Fix local serving when package `individually: true` and function `module` are provided (#98) +- Fix Flask CLI invocation of built-in commands (#99) + + _Mischa Spiegelmock_ # 1.7.2 diff --git a/index.js b/index.js index d16f7ee..48b1964 100644 --- a/index.js +++ b/index.js @@ -387,6 +387,7 @@ class ServerlessWSGI { const host = this.options.host || "localhost"; const disable_threading = this.options["disable-threading"] || false; const num_processes = this.options["num-processes"] || 1; + const ssl = this.options.ssl || false; var args = [ path.resolve(__dirname, "serve.py"), @@ -404,6 +405,10 @@ class ServerlessWSGI { args.push("--disable-threading"); } + if (ssl) { + args.push("--ssl"); + } + var status = child_process.spawnSync(this.pythonBin, args, { stdio: "inherit" }); @@ -557,6 +562,9 @@ class ServerlessWSGI { }, "num-processes": { usage: "Number of processes for server, defaults to 1" + }, + ssl: { + usage: "Enable local serving using HTTPS" } } }, diff --git a/index.test.js b/index.test.js index f5c02d9..d5c824b 100644 --- a/index.test.js +++ b/index.test.js @@ -1278,6 +1278,43 @@ describe("serverless-wsgi", () => { }); }); + it("allows serving over https", () => { + var plugin = new Plugin( + { + config: { servicePath: "/tmp" }, + service: { + provider: { runtime: "python2.7" }, + custom: { wsgi: { app: "api.app" } } + }, + classes: { Error: Error }, + cli: { log: () => {} } + }, + { ssl: true } + ); + + var sandbox = sinon.createSandbox(); + var hasbinStub = sandbox.stub(hasbin, "sync").returns(true); + var procStub = sandbox.stub(child_process, "spawnSync").returns({}); + plugin.hooks["wsgi:serve:serve"]().then(() => { + expect(hasbinStub.calledWith("python2.7")).to.be.true; + expect( + procStub.calledWith( + "python2.7", + [ + path.resolve(__dirname, "serve.py"), + "/tmp", + "api.app", + 5000, + "localhost", + "--ssl" + ], + { stdio: "inherit" } + ) + ).to.be.true; + sandbox.restore(); + }); + }); + it("loads environment variables", () => { var plugin = new Plugin( { diff --git a/serve.py b/serve.py index 4d67c97..fc9e14e 100644 --- a/serve.py +++ b/serve.py @@ -34,10 +34,13 @@ def parse_args(): # pragma: no cover parser.add_argument("--disable-threading", action="store_false", dest="use_threads") parser.add_argument("--num-processes", type=int, dest="processes", default=1) + # Optional serving using HTTPS + parser.add_argument("--ssl", action="store_true", dest="ssl") + return parser.parse_args() -def serve(cwd, app, port=5000, host="localhost", threaded=True, processes=1): +def serve(cwd, app, port=5000, host="localhost", threaded=True, processes=1, ssl=False): sys.path.insert(0, cwd) os.environ["IS_OFFLINE"] = "True" @@ -49,6 +52,11 @@ def serve(cwd, app, port=5000, host="localhost", threaded=True, processes=1): wsgi_module = importlib.import_module(wsgi_fqn_parts[-1]) wsgi_app = getattr(wsgi_module, wsgi_fqn[1]) + if ssl: + ssl_context = "adhoc" + else: + ssl_context = None + # Attempt to force Flask into debug mode try: wsgi_app.debug = True @@ -64,6 +72,7 @@ def serve(cwd, app, port=5000, host="localhost", threaded=True, processes=1): use_evalex=True, threaded=threaded, processes=processes, + ssl_context=ssl_context, ) @@ -77,4 +86,5 @@ def serve(cwd, app, port=5000, host="localhost", threaded=True, processes=1): host=args.host, threaded=args.use_threads, processes=args.processes, + ssl=args.ssl, ) diff --git a/serve_test.py b/serve_test.py index ca44f5e..7713eaf 100644 --- a/serve_test.py +++ b/serve_test.py @@ -60,6 +60,7 @@ def test_serve(mock_path, mock_importlib, mock_werkzeug): "use_evalex": True, "threaded": True, "processes": 1, + "ssl_context": None, } @@ -77,6 +78,7 @@ def test_serve_alternative_hostname(mock_path, mock_importlib, mock_werkzeug): "use_evalex": True, "threaded": True, "processes": 1, + "ssl_context": None, } @@ -94,6 +96,7 @@ def test_serve_disable_threading(mock_path, mock_importlib, mock_werkzeug): "use_evalex": True, "threaded": False, "processes": 1, + "ssl_context": None, } @@ -111,6 +114,25 @@ def test_serve_multiple_processes(mock_path, mock_importlib, mock_werkzeug): "use_evalex": True, "threaded": True, "processes": 10, + "ssl_context": None, + } + + +def test_serve_ssl(mock_path, mock_importlib, mock_werkzeug): + serve.serve("/tmp1", "app.app", "5000", "0.0.0.0", threaded=False, ssl=True) + assert len(mock_path) == 1 + assert mock_path[0] == "/tmp1" + assert mock_werkzeug.lastcall.host == "0.0.0.0" + assert mock_werkzeug.lastcall.port == 5000 + assert mock_werkzeug.lastcall.app.module == "app" + assert mock_werkzeug.lastcall.app.debug + assert mock_werkzeug.lastcall.kwargs == { + "use_reloader": True, + "use_debugger": True, + "use_evalex": True, + "threaded": False, + "processes": 1, + "ssl_context": "adhoc", } @@ -129,6 +151,7 @@ def test_serve_from_subdir(mock_path, mock_importlib, mock_werkzeug): "use_evalex": True, "threaded": True, "processes": 1, + "ssl_context": None, }