Skip to content

Commit

Permalink
service: http: Command line option to redirect URLs
Browse files Browse the repository at this point in the history
Add the ability to redirect one URL to another via a 307 Temporary
Redirect plus Location header response for a given HTTP method and path.

Fixes: intel#746

Signed-off-by: John Andersen <[email protected]>
  • Loading branch information
pdxjohnny committed Jul 14, 2020
1 parent 98b7573 commit 10dba59
Show file tree
Hide file tree
Showing 5 changed files with 128 additions and 1 deletion.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Example showing usage of locks in dataflow.
- `-skip` flag to `service dev install` command to let users not install certain
core plugins
- HTTP service got a `-redirect` flag which allows for URL redirection via a
HTTP 307 response
### Changed
- Renamed `-seed` to `-inputs` in `dataflow create` command
- Renamed configloader/png to configloader/image and added support for loading JPEG and TIFF file formats
Expand Down
41 changes: 41 additions & 0 deletions service/http/dffml_service_http/cli.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import ssl
import asyncio
import inspect
import argparse
import subprocess
from typing import List
Expand Down Expand Up @@ -170,6 +171,41 @@ class MultiCommCMD(CMD):
CONFIG = MultiCommCMDConfig


class RedirectFormatError(Exception):
"""
Raises when -redirects was not divisible by 3
"""


class ParseRedirectsAction(argparse.Action):
def __call__(self, parser, namespace, values, option_string=None):
if not isinstance(values, list):
values = [values]
if len(values) % 3 != 0:
raise RedirectFormatError(
inspect.cleandoc(
r"""
-redirect usage: METHOD SOURCE_PATH DESTINATION_PATH
Examples
--------
Redirect / to /index.html for GET requests
-redirect GET / /index.html
Redirect / to /index.html for GET requests and /signup to
/mysignup for POST requests
-redirect \
GET / /index.html \
POST /signup /mysignup
"""
).strip()
)
setattr(namespace, self.dest, values)


@config
class ServerConfig(TLSCMDConfig, MultiCommCMDConfig):
port: int = field(
Expand Down Expand Up @@ -208,6 +244,11 @@ class ServerConfig(TLSCMDConfig, MultiCommCMDConfig):
action=list_action(Sources),
labeled=True,
)
redirect: List[str] = field(
"list of METHOD SOURCE_PATH DESTINATION_PATH pairs, number of elements must be divisible by 3",
action=ParseRedirectsAction,
default_factory=lambda: [],
)


class Server(TLSCMD, MultiCommCMD, Routes):
Expand Down
14 changes: 14 additions & 0 deletions service/http/dffml_service_http/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -760,6 +760,15 @@ async def api_js(self, request):
headers={"Content-Type": "application/javascript"},
)

def mkredirect(self, destination_path):
async def redirector(request):
return web.Response(
status=HTTPStatus.TEMPORARY_REDIRECT,
headers={"Location": destination_path},
)

return redirector

async def on_shutdown(self, app):
self.logger.debug("Shutting down service and exiting all contexts")
await app["exit_stack"].__aexit__(None, None, None)
Expand Down Expand Up @@ -878,6 +887,11 @@ async def setup(self, **kwargs):
),
]
)
# Redirects
for method, src, dst in (
self.redirect[i : i + 3] for i in range(0, len(self.redirect), 3)
):
self.routes.append((method.upper(), src, self.mkredirect(dst)))
# Serve api.js
if self.js:
self.routes.append(("GET", "/api.js", self.api_js))
Expand Down
29 changes: 29 additions & 0 deletions service/http/docs/cli.rst
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,32 @@ for record retrieval or updating, use the ``-sources`` flag.
$ dffml service http server \
-sources mysource=csv \
-source-mysource-filename training.csv
Redirects
---------

You might want to have the HTTP service redirect one URL to another. You can do
this using the ``-redirect`` flag. Syntax is as follows

.. code-block::
-redirect \
METHOD_1 SOURCE_PATH_1 DESTINATION_PATH_1 \
METHOD_2 SOURCE_PATH_2 DESTINATION_PATH_2 \
METHOD_3 SOURCE_PATH_3 DESTINATION_PATH_3 \
...
Example of redirecting ``/`` to ``/index.html`` for GET requests

.. code-block::
-redirect GET / /index.html
Redirect ``/`` to ``/index.html`` for GET requests and ``/signup`` to
``/mysignup`` for POST requests

.. code-block::
-redirect \
GET / /index.html \
POST /signup /mysignup
43 changes: 42 additions & 1 deletion service/http/tests/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
from dffml import Record, Features, Feature, save, train, accuracy
from dffml.util.asynctestcase import AsyncTestCase

from dffml_service_http.cli import HTTPService
from dffml_service_http.cli import HTTPService, RedirectFormatError
from dffml_service_http.util.testing import ServerRunner, ServerException

from .test_routes import TestRoutesMultiComm
Expand Down Expand Up @@ -292,3 +292,44 @@ async def test_sources(self):
cli, "/source/mysource/record/myrecord"
) as r:
self.assertEqual(await r.json(), myrecord.export())

async def test_redirect_format_error(self):
with self.assertRaises(RedirectFormatError):
async with ServerRunner.patch(HTTPService.server) as tserver:
await tserver.start(
# Missing METHOD
HTTPService.server.cli(
"-insecure",
"-port",
"0",
"-redirect",
"/",
"/index.html",
)
)

async def test_redirect(self):
with tempfile.TemporaryDirectory() as tempdir:
pathlib.Path(tempdir, "index.html").write_text("Hello World")
pathlib.Path(tempdir, "mysignup").write_text("MySignUp")
async with ServerRunner.patch(HTTPService.server) as tserver:
cli = await tserver.start(
HTTPService.server.cli(
"-insecure",
"-port",
"0",
"-static",
tempdir,
"-redirect",
"GET",
"/",
"/index.html",
"GET",
"/signup",
"/mysignup",
)
)
async with self.get(cli, "/") as r:
self.assertEqual(await r.text(), "Hello World")
async with self.get(cli, "/signup") as r:
self.assertEqual(await r.text(), "MySignUp")

0 comments on commit 10dba59

Please sign in to comment.