sidex 1.5.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- sidex/__init__.py +42 -0
- sidex/client.py +76 -0
- sidex/dump.py +78 -0
- sidex/request.py +50 -0
- sidex/server.py +56 -0
- sidex/setup.py +674 -0
- sidex-1.5.1.dist-info/METADATA +72 -0
- sidex-1.5.1.dist-info/RECORD +10 -0
- sidex-1.5.1.dist-info/WHEEL +5 -0
- sidex-1.5.1.dist-info/top_level.txt +3 -0
sidex/__init__.py
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
''' Simple Data Exchange Server
|
|
4
|
+
|
|
5
|
+
This module provides a minimal framework to launch a file server
|
|
6
|
+
running on the middleware `Flask`. You are allowed to fetch, put,
|
|
7
|
+
and delete files over HTTP.
|
|
8
|
+
|
|
9
|
+
You can launch a server on the command line as follows:
|
|
10
|
+
|
|
11
|
+
python -m sidex.server --host localhost --port 8000 TARGET_DIR
|
|
12
|
+
|
|
13
|
+
A file in the TARGET_DIR can be fetched by `wget`:
|
|
14
|
+
|
|
15
|
+
wget --post-data="method=get" http://localhost:8000/FILENAME
|
|
16
|
+
|
|
17
|
+
or by using `sidex.client`:
|
|
18
|
+
|
|
19
|
+
python -m sidex.client http://localhost:8080/FILENAME
|
|
20
|
+
|
|
21
|
+
The `POST` method is required since the `method` data is mandatory.
|
|
22
|
+
The `get` method can be protected by setting a token.
|
|
23
|
+
|
|
24
|
+
The `put` and `delete` methods are disabled by default. They can be
|
|
25
|
+
enabled by setting tokens. Note that the token is just a passphrase
|
|
26
|
+
and not ciphered at all. The transaction is never protected. Do not
|
|
27
|
+
use the SIDEX in case that the network is untrastrul.
|
|
28
|
+
|
|
29
|
+
The SIDEX provides a way to customize the functions. The default
|
|
30
|
+
behaviors of any methods can be overridden.
|
|
31
|
+
'''
|
|
32
|
+
|
|
33
|
+
from .setup import setup
|
|
34
|
+
from .request import request
|
|
35
|
+
|
|
36
|
+
__version__ = '1.5.1'
|
|
37
|
+
|
|
38
|
+
__all__ = [
|
|
39
|
+
'__version__',
|
|
40
|
+
'setup',
|
|
41
|
+
'request',
|
|
42
|
+
]
|
sidex/client.py
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
''' A command-line SIDEX client.
|
|
4
|
+
|
|
5
|
+
This file provides a command-line SIDEX client.
|
|
6
|
+
A detailed usage is available by typing the following command:
|
|
7
|
+
|
|
8
|
+
python -m sidex.client -h
|
|
9
|
+
'''
|
|
10
|
+
|
|
11
|
+
import os, sys, re, requests
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
if __name__ == '__main__':
|
|
15
|
+
from argparse import ArgumentParser as ap
|
|
16
|
+
parser = ap(prog='client', description='SIDEX minimal client')
|
|
17
|
+
parser.add_argument(
|
|
18
|
+
'filename', type=str, nargs='?',
|
|
19
|
+
help='filename to be uploaded (only requred in put mode)')
|
|
20
|
+
parser.add_argument(
|
|
21
|
+
'target', type=str,
|
|
22
|
+
help='address to SIDEX server')
|
|
23
|
+
parser.add_argument(
|
|
24
|
+
'-d', '--delete', action='store_true',
|
|
25
|
+
help='delete file')
|
|
26
|
+
parser.add_argument(
|
|
27
|
+
'-p', '--ping', action='store_true',
|
|
28
|
+
help='send ping message')
|
|
29
|
+
parser.add_argument(
|
|
30
|
+
'--token', metavar='token', type=str,
|
|
31
|
+
help='set token')
|
|
32
|
+
|
|
33
|
+
args = parser.parse_args()
|
|
34
|
+
# Leading "http://" can be omitted.
|
|
35
|
+
if not re.match('^https?://', args.target):
|
|
36
|
+
args.target = 'http://' + args.target
|
|
37
|
+
eprint = lambda s: print('error: '+s, file=sys.stderr)
|
|
38
|
+
|
|
39
|
+
if args.delete is True and args.filename is not None:
|
|
40
|
+
eprint('option conflicted.')
|
|
41
|
+
exit(1)
|
|
42
|
+
|
|
43
|
+
method = 'get'
|
|
44
|
+
files = None
|
|
45
|
+
filename = os.path.basename(args.target)
|
|
46
|
+
|
|
47
|
+
if args.ping is True:
|
|
48
|
+
method = 'ping'
|
|
49
|
+
if args.delete is True:
|
|
50
|
+
method = 'delete'
|
|
51
|
+
if args.filename is not None:
|
|
52
|
+
method = 'put'
|
|
53
|
+
with open(args.filename, 'rb') as f:
|
|
54
|
+
files = {'payload': f.read()}
|
|
55
|
+
|
|
56
|
+
if method == 'get' and os.path.exists(filename):
|
|
57
|
+
eprint('file "{}" already exists.'.format(filename))
|
|
58
|
+
exit(1)
|
|
59
|
+
|
|
60
|
+
try:
|
|
61
|
+
data = {'method': method, 'token': args.token}
|
|
62
|
+
req = requests.post(args.target, data=data, files=files)
|
|
63
|
+
if req.ok is False:
|
|
64
|
+
eprint(req.text.strip())
|
|
65
|
+
req.raise_for_status()
|
|
66
|
+
|
|
67
|
+
if method == 'get':
|
|
68
|
+
with open(filename, 'wb') as f:
|
|
69
|
+
f.write(req.content)
|
|
70
|
+
elif method == 'ping':
|
|
71
|
+
pass
|
|
72
|
+
else:
|
|
73
|
+
print(req.text.strip())
|
|
74
|
+
except Exception as e:
|
|
75
|
+
eprint(str(e))
|
|
76
|
+
exit(1)
|
sidex/dump.py
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
''' A command-line SIDEX client for dump.
|
|
4
|
+
|
|
5
|
+
This file provides a command-line SIDEX client.
|
|
6
|
+
A detailed usage is available by typing the following command:
|
|
7
|
+
|
|
8
|
+
python -m sidex.dump -h
|
|
9
|
+
'''
|
|
10
|
+
|
|
11
|
+
import os, sys, re, requests
|
|
12
|
+
import io, gzip, bz2, lzma, tarfile
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
if __name__ == '__main__':
|
|
16
|
+
from argparse import ArgumentParser as ap
|
|
17
|
+
parser = ap(prog='dump', description='SIDEX dump client')
|
|
18
|
+
parser.add_argument('target', type=str,
|
|
19
|
+
help='address to SIDEX server')
|
|
20
|
+
parser.add_argument('filename', type=str, nargs='+',
|
|
21
|
+
help='requested filename')
|
|
22
|
+
parser.add_argument(
|
|
23
|
+
'-f', '--overwrite', dest='overwrite', action='store_true',
|
|
24
|
+
help='overwrite files even if exists')
|
|
25
|
+
parser.add_argument(
|
|
26
|
+
'--tar', dest='tarball', type=str, action='store',
|
|
27
|
+
help='grab files as a tarball archive')
|
|
28
|
+
parser.add_argument(
|
|
29
|
+
'--token', dest='token', metavar='token', type=str,
|
|
30
|
+
help='set token')
|
|
31
|
+
|
|
32
|
+
args = parser.parse_args()
|
|
33
|
+
# Leading "http://" can be omitted.
|
|
34
|
+
if not re.match('^https?://', args.target):
|
|
35
|
+
args.target = 'http://' + args.target
|
|
36
|
+
eprint = lambda s: print('error: '+s, file=sys.stderr)
|
|
37
|
+
|
|
38
|
+
method = 'dump'
|
|
39
|
+
|
|
40
|
+
if not args.overwrite:
|
|
41
|
+
for f in args.filename:
|
|
42
|
+
if os.path.exists(f):
|
|
43
|
+
eprint('file "{}" already exists.'.format(f))
|
|
44
|
+
exit(1)
|
|
45
|
+
|
|
46
|
+
try:
|
|
47
|
+
data = {
|
|
48
|
+
'method': 'dump',
|
|
49
|
+
'token': args.token,
|
|
50
|
+
'filename': args.filename,
|
|
51
|
+
}
|
|
52
|
+
with requests.post(args.target, data=data, stream=True) as req:
|
|
53
|
+
if req.ok is False:
|
|
54
|
+
eprint(req.text.strip())
|
|
55
|
+
req.raise_for_status()
|
|
56
|
+
|
|
57
|
+
if args.tarball:
|
|
58
|
+
dummy, ext = os.path.splitext(args.tarball)
|
|
59
|
+
print(ext)
|
|
60
|
+
if ext == '.gz':
|
|
61
|
+
with gzip.open(args.tarball, 'wb') as arv:
|
|
62
|
+
for chunk in req.iter_content(65535): arv.write(chunk)
|
|
63
|
+
elif ext == '.bz2':
|
|
64
|
+
with bz2.open(args.tarball, 'wb') as arv:
|
|
65
|
+
for chunk in req.iter_content(65535): arv.write(chunk)
|
|
66
|
+
elif ext == '.xz':
|
|
67
|
+
with lzma.open(args.tarball, 'wb') as arv:
|
|
68
|
+
for chunk in req.iter_content(65535): arv.write(chunk)
|
|
69
|
+
else:
|
|
70
|
+
with open(args.tarball, 'wb') as arv:
|
|
71
|
+
for chunk in req.iter_content(65535): arv.write(chunk)
|
|
72
|
+
else:
|
|
73
|
+
buf = io.BytesIO(req.content)
|
|
74
|
+
with tarfile.open(fileobj=buf, mode='r:') as arv:
|
|
75
|
+
arv.extractall()
|
|
76
|
+
except Exception as e:
|
|
77
|
+
eprint(str(e))
|
|
78
|
+
exit(1)
|
sidex/request.py
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
''' A function for a quick access to a sidex server.
|
|
4
|
+
|
|
5
|
+
This module provides a helper function `sidex_request` to make
|
|
6
|
+
a query to a sidex server.
|
|
7
|
+
'''
|
|
8
|
+
import os, requests
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def request(url, method, filename=None, token=None):
|
|
12
|
+
''' Make a request to a sidex server.
|
|
13
|
+
|
|
14
|
+
Arguments:
|
|
15
|
+
url (str):
|
|
16
|
+
The url, which defines a query to a sidex server.
|
|
17
|
+
method (str):
|
|
18
|
+
The name of the requested method. The available methods
|
|
19
|
+
are `get`, `put`, and `delete`.
|
|
20
|
+
filename (str, optional):
|
|
21
|
+
A filename to be uploaded to a sidex server. This
|
|
22
|
+
argument is only required in the 'put' method.
|
|
23
|
+
token (str, optional):
|
|
24
|
+
A token string passed to a sidex server. This argument will
|
|
25
|
+
be ignored when the server is not protecte by token.
|
|
26
|
+
|
|
27
|
+
Returns:
|
|
28
|
+
requests.Response:
|
|
29
|
+
A response from a sidex server.
|
|
30
|
+
'''
|
|
31
|
+
method = 'get'
|
|
32
|
+
|
|
33
|
+
if method not in ('get', 'put', 'delete'):
|
|
34
|
+
raise RuntimeError('invalid method.')
|
|
35
|
+
if method == 'put' and filename is None:
|
|
36
|
+
raise RuntimeError('upload file is not specified.')
|
|
37
|
+
data = {'method': method, 'token': token}
|
|
38
|
+
|
|
39
|
+
if method == 'get':
|
|
40
|
+
if os.path.exists(filename):
|
|
41
|
+
raise RuntimeError('file "{}" already exists'.format(filename))
|
|
42
|
+
return requests.post(url, data=data)
|
|
43
|
+
elif method == 'put':
|
|
44
|
+
with open(filename, 'rb') as f:
|
|
45
|
+
files = {
|
|
46
|
+
'payload': f.read(),
|
|
47
|
+
}
|
|
48
|
+
return requests.post(url, data=data, files=files)
|
|
49
|
+
elif method == 'delete':
|
|
50
|
+
return requests.post(url, data=data)
|
sidex/server.py
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
''' A command-line SIDEX server.
|
|
4
|
+
|
|
5
|
+
This file provides a command-line SIDEX server.
|
|
6
|
+
A detailed usage is available by typing the following command:
|
|
7
|
+
|
|
8
|
+
python -m sidex.server -h
|
|
9
|
+
'''
|
|
10
|
+
|
|
11
|
+
from . setup import setup
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
if __name__ == '__main__':
|
|
15
|
+
from argparse import ArgumentParser as ap
|
|
16
|
+
import logging
|
|
17
|
+
parser = ap(prog='server', description='sidex server process')
|
|
18
|
+
parser.add_argument(
|
|
19
|
+
'target', type=str,
|
|
20
|
+
help='target directory')
|
|
21
|
+
parser.add_argument(
|
|
22
|
+
'--host', metavar='host', type=str, default='0.0.0.0',
|
|
23
|
+
help='set server hostname')
|
|
24
|
+
parser.add_argument(
|
|
25
|
+
'--port', metavar='port', type=int, default=8080,
|
|
26
|
+
help='set server port number')
|
|
27
|
+
parser.add_argument(
|
|
28
|
+
'--get-token', metavar='token', type=str,
|
|
29
|
+
help='limit get function by setting token')
|
|
30
|
+
parser.add_argument(
|
|
31
|
+
'--put-token', metavar='token', type=str,
|
|
32
|
+
help='enable put function by setting token')
|
|
33
|
+
parser.add_argument(
|
|
34
|
+
'--delete-token', metavar='token', type=str,
|
|
35
|
+
help='enable delete function by setting token.')
|
|
36
|
+
parser.add_argument(
|
|
37
|
+
'--subdir', metavar='subdir', type=str,
|
|
38
|
+
help='set subdirectory')
|
|
39
|
+
parser.add_argument(
|
|
40
|
+
'--debug', action='store_true',
|
|
41
|
+
help='enable debug messages')
|
|
42
|
+
|
|
43
|
+
args = parser.parse_args()
|
|
44
|
+
|
|
45
|
+
log_level = 'DEBUG' if args.debug else 'INFO'
|
|
46
|
+
log_handler = logging.StreamHandler()
|
|
47
|
+
log_handler.setFormatter(logging.Formatter(
|
|
48
|
+
fmt='[%(asctime)s] %(levelname)s:%(name)s:%(message)s',
|
|
49
|
+
datefmt='%Y-%m-%d %H:%M:%S'))
|
|
50
|
+
|
|
51
|
+
server = setup(
|
|
52
|
+
args.target, subdir=args.subdir,
|
|
53
|
+
get_token=args.get_token, put_token=args.put_token,
|
|
54
|
+
delete_token=args.delete_token,
|
|
55
|
+
log_handler=log_handler, log_level=log_level)
|
|
56
|
+
server.run(host=args.host, port=args.port, threaded=True, debug=args.debug)
|
sidex/setup.py
ADDED
|
@@ -0,0 +1,674 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
''' SIDEX miscellaneous functions.
|
|
4
|
+
|
|
5
|
+
This module provides miscellaneous functions to setup the SIDEX server.
|
|
6
|
+
The behaviours of the `get`, `put`, and `delete` methods are defined.
|
|
7
|
+
|
|
8
|
+
Use the `setup` function to launch a customized SIDEX server.
|
|
9
|
+
'''
|
|
10
|
+
from flask import Flask, Response
|
|
11
|
+
from flask import request, url_for
|
|
12
|
+
import os, io, tarfile, logging
|
|
13
|
+
|
|
14
|
+
werkzeug = logging.getLogger('werkzeug')
|
|
15
|
+
werkzeug.setLevel('ERROR')
|
|
16
|
+
|
|
17
|
+
app = Flask(__name__)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@app.context_processor
|
|
21
|
+
def override_url_for():
|
|
22
|
+
''' A helper function to construct a url with `subdir` option.
|
|
23
|
+
|
|
24
|
+
This does nothing when the `subdir` option is not defined.
|
|
25
|
+
The url is modified in case that the `subdir` is given. The modified
|
|
26
|
+
`url_for` function accepts the following arguments.
|
|
27
|
+
|
|
28
|
+
Arguments:
|
|
29
|
+
endpoint (str):
|
|
30
|
+
The relative path to the requested resource.
|
|
31
|
+
*Arguments:
|
|
32
|
+
Variable length argument list.
|
|
33
|
+
**options:
|
|
34
|
+
Arbitrary option arguments.
|
|
35
|
+
|
|
36
|
+
Returns:
|
|
37
|
+
str: A constructed url to the requeste resource.
|
|
38
|
+
'''
|
|
39
|
+
def url_for_subdir(endpoint, *args, **options):
|
|
40
|
+
subdir = '/{}'.format(app.subdir) if app.subdir else ''
|
|
41
|
+
return subdir+url_for(endpoint, *args, **options)
|
|
42
|
+
return dict(url_for=url_for_subdir)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def eprint(message, exc_info=None):
|
|
46
|
+
''' Print an error message in the log file.
|
|
47
|
+
|
|
48
|
+
Arguments:
|
|
49
|
+
message (str):
|
|
50
|
+
The body of the error message.
|
|
51
|
+
exc_info (Exception,optional):
|
|
52
|
+
The Exception instance to print traceback information.
|
|
53
|
+
'''
|
|
54
|
+
remote = request.remote_addr
|
|
55
|
+
app.logger.error(remote+':{}'.format(message), exc_info=exc_info)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def iprint(message):
|
|
59
|
+
''' Print an info message in the log file.
|
|
60
|
+
|
|
61
|
+
Arguments:
|
|
62
|
+
message (str):
|
|
63
|
+
The body of the information message.
|
|
64
|
+
'''
|
|
65
|
+
remote = request.remote_addr
|
|
66
|
+
app.logger.info(remote+':{}'.format(message))
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def dprint(message):
|
|
70
|
+
''' Print a debug message in the log file.
|
|
71
|
+
|
|
72
|
+
Arguments:
|
|
73
|
+
message (str):
|
|
74
|
+
The body of the debug message.
|
|
75
|
+
'''
|
|
76
|
+
remote = request.remote_addr
|
|
77
|
+
app.logger.debug(remote+':{}'.format(message))
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def invalid_path(path):
|
|
81
|
+
''' Check whether the <path> looks invalid.
|
|
82
|
+
|
|
83
|
+
Arguments:
|
|
84
|
+
path (str):
|
|
85
|
+
The path to the file to be checked.
|
|
86
|
+
|
|
87
|
+
Returns:
|
|
88
|
+
bool:
|
|
89
|
+
`true` when the <path> contains any invalid sequences.
|
|
90
|
+
'''
|
|
91
|
+
return '../' in path
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def default_delete_function(req, local_path, **options):
|
|
95
|
+
''' Define the default behavior of the `delete` method.
|
|
96
|
+
|
|
97
|
+
This function removes the file located at <local_path>.
|
|
98
|
+
|
|
99
|
+
Arguments:
|
|
100
|
+
req (flask.request):
|
|
101
|
+
The request instance given by Flask.
|
|
102
|
+
local_path (str):
|
|
103
|
+
The absolute path to the requested resource.
|
|
104
|
+
**options:
|
|
105
|
+
Arbitrary option arguments.
|
|
106
|
+
Currently no option is passed to this function.
|
|
107
|
+
|
|
108
|
+
Returns:
|
|
109
|
+
flask.Response:
|
|
110
|
+
A response message.
|
|
111
|
+
'''
|
|
112
|
+
filename = os.path.basename(local_path)
|
|
113
|
+
os.unlink(local_path)
|
|
114
|
+
return Response('"{}" successfully deleted.\n'.format(filename))
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def delete(req, target, **options):
|
|
118
|
+
''' Provide the `delete` method.
|
|
119
|
+
|
|
120
|
+
This function is called when the `delete` method is selected.
|
|
121
|
+
The <app.delete_function> is called internally. When the customized
|
|
122
|
+
`delete_function` is specified, the behavior of the `delete` method
|
|
123
|
+
is overridden.
|
|
124
|
+
|
|
125
|
+
This method is disabled by default. To enable the `delete` method,
|
|
126
|
+
the application should be setup with `delete_token`. The token is
|
|
127
|
+
reffered to as <app.delete_token>.
|
|
128
|
+
|
|
129
|
+
The request must contain the `token` field. When the `token` is not
|
|
130
|
+
available, this always returns an error message.
|
|
131
|
+
In case that the `token` does not equal to <app.delete_token>,
|
|
132
|
+
an error message will be returned.
|
|
133
|
+
|
|
134
|
+
Arguments:
|
|
135
|
+
req (flask.request):
|
|
136
|
+
A request instance generated by Flask.
|
|
137
|
+
target (str):
|
|
138
|
+
The path to the requested resource.
|
|
139
|
+
**options:
|
|
140
|
+
Arbitrary option arguments.
|
|
141
|
+
Currently no option is passed to this function.
|
|
142
|
+
|
|
143
|
+
Returns:
|
|
144
|
+
flask.Response:
|
|
145
|
+
A response message.
|
|
146
|
+
'''
|
|
147
|
+
local_path = '{}/{}'.format(app.workdir, target)
|
|
148
|
+
filename = os.path.basename(local_path)
|
|
149
|
+
dirname = os.path.dirname(local_path)
|
|
150
|
+
emsg = lambda s: 'cannot delete "{}": {{}}.\n'.format(target).format(s)
|
|
151
|
+
# check if a valid token is given.
|
|
152
|
+
if app.delete_token is None:
|
|
153
|
+
eprint('disabled function "delete" called.')
|
|
154
|
+
return Response(emsg('function disabled'), status=400)
|
|
155
|
+
token = req.form.get('token')
|
|
156
|
+
dprint('token = "{}"'.format(token))
|
|
157
|
+
if app.delete_token is not None and token != app.delete_token:
|
|
158
|
+
return Response(emsg('invalid token'), status=400)
|
|
159
|
+
# assert path seems valid.
|
|
160
|
+
if invalid_path(target):
|
|
161
|
+
eprint('invalid path: {}'.format(target))
|
|
162
|
+
return Response(emsg('invalid path'), status=400)
|
|
163
|
+
# delete a file.
|
|
164
|
+
try:
|
|
165
|
+
return app.delete_function(req, local_path, **options)
|
|
166
|
+
except FileNotFoundError as e:
|
|
167
|
+
eprint(str(e))
|
|
168
|
+
return Response(emsg('file not found'), status=404)
|
|
169
|
+
except Exception as e:
|
|
170
|
+
eprint(str(e))
|
|
171
|
+
errmsg = emsg(f'unexpected error: {str(e)} ({e.__class__.__name__})')
|
|
172
|
+
return Response(errmsg, status=500)
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def default_put_function(req, local_path, **options):
|
|
176
|
+
''' Define the default behavior of the `put` method.
|
|
177
|
+
|
|
178
|
+
This function create a file stored in <req> at <local_path>.
|
|
179
|
+
This fails when the local directory does not exist. Note that
|
|
180
|
+
any existing file cannot be overwritten.
|
|
181
|
+
|
|
182
|
+
Arguments:
|
|
183
|
+
req (flask.request):
|
|
184
|
+
The request instance given by Flask.
|
|
185
|
+
local_path (str):
|
|
186
|
+
The absolute path to the requested resource.
|
|
187
|
+
**options:
|
|
188
|
+
Arbitrary option arguments.
|
|
189
|
+
Currently no option is passed to this function.
|
|
190
|
+
|
|
191
|
+
Returns:
|
|
192
|
+
flask.Response:
|
|
193
|
+
A response message.
|
|
194
|
+
'''
|
|
195
|
+
filename = os.path.basename(local_path)
|
|
196
|
+
dirname = os.path.dirname(local_path)
|
|
197
|
+
req.files['payload'].save(local_path)
|
|
198
|
+
return Response('successfully uploaded to "{}".\n'.format(filename))
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def put(req, target, **options):
|
|
202
|
+
''' Provide the `put` method.
|
|
203
|
+
|
|
204
|
+
This function is called when the `put` method is selected.
|
|
205
|
+
The <app.put_function> is called internally. When the customized
|
|
206
|
+
`put_function` is specified, the behavior of the `put` method
|
|
207
|
+
is overridden.
|
|
208
|
+
|
|
209
|
+
This method is disabled by default. To enable the `put` method,
|
|
210
|
+
the application should be setup with `put_token`. The token is
|
|
211
|
+
reffered to as <app.put_token>.
|
|
212
|
+
|
|
213
|
+
The request must contain the `token` field. When the `token` is not
|
|
214
|
+
available, this always returns an error message.
|
|
215
|
+
In case that the `token` does not equal to <app.put_token>,
|
|
216
|
+
an error message will be returned.
|
|
217
|
+
|
|
218
|
+
Arguments:
|
|
219
|
+
req (flask.request):
|
|
220
|
+
A request instance generated by Flask.
|
|
221
|
+
target (str):
|
|
222
|
+
The path to the requested resource.
|
|
223
|
+
**options:
|
|
224
|
+
Arbitrary option arguments.
|
|
225
|
+
Currently no option is passed to this function.
|
|
226
|
+
|
|
227
|
+
Returns:
|
|
228
|
+
flask.Response:
|
|
229
|
+
A response message.
|
|
230
|
+
'''
|
|
231
|
+
local_path = '{}/{}'.format(app.workdir, target)
|
|
232
|
+
filename = os.path.basename(local_path)
|
|
233
|
+
emsg = lambda s: 'cannot create "{}": {{}}.\n'.format(target).format(s)
|
|
234
|
+
# check if a valid token is given.
|
|
235
|
+
if app.put_token is None:
|
|
236
|
+
eprint('disabled function "put" called.')
|
|
237
|
+
return Response(emsg('function disabled'), status=400)
|
|
238
|
+
token = req.form.get('token')
|
|
239
|
+
dprint('token = "{}"'.format(token))
|
|
240
|
+
if app.put_token is not None and token != app.put_token:
|
|
241
|
+
return Response(emsg('invalid token'), status=400)
|
|
242
|
+
# assert path seems valid.
|
|
243
|
+
if invalid_path(target):
|
|
244
|
+
eprint('invalid path: {}'.format(target))
|
|
245
|
+
return Response(emsg('invalid path'), status=400)
|
|
246
|
+
# overwrite is not allowed.
|
|
247
|
+
if os.path.exists(local_path):
|
|
248
|
+
eprint('file "{}" already exists.'.format(local_path))
|
|
249
|
+
return Response(emsg('cannot overwrite files'), status=400)
|
|
250
|
+
# create a file.
|
|
251
|
+
if 'payload' not in req.files:
|
|
252
|
+
return Response(emsg('"payload" is required'), status=400)
|
|
253
|
+
try:
|
|
254
|
+
return app.put_function(req, local_path, **options)
|
|
255
|
+
except FileNotFoundError as e:
|
|
256
|
+
eprint(str(e))
|
|
257
|
+
return Response(emsg('file not found'), status=404)
|
|
258
|
+
except Exception as e:
|
|
259
|
+
eprint(str(e))
|
|
260
|
+
errmsg = emsg(f'unexpected error: {str(e)} ({e.__class__.__name__})')
|
|
261
|
+
return Response(errmsg, status=500)
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
def default_get_function(req, local_path, **options):
|
|
265
|
+
''' Define the default behavior of the `get` method.
|
|
266
|
+
|
|
267
|
+
Returns the file content located at <local_path>.
|
|
268
|
+
|
|
269
|
+
Arguments:
|
|
270
|
+
req (flask.request):
|
|
271
|
+
The request instance given by Flask.
|
|
272
|
+
local_path (str):
|
|
273
|
+
The absolute path to the requested resource.
|
|
274
|
+
**options:
|
|
275
|
+
Arbitrary option arguments.
|
|
276
|
+
Currently no option is passed to this function.
|
|
277
|
+
|
|
278
|
+
Returns:
|
|
279
|
+
flask.Response:
|
|
280
|
+
A response message.
|
|
281
|
+
'''
|
|
282
|
+
with open(local_path, 'rb') as f:
|
|
283
|
+
return Response(f.read(), mimetype='application/octet-stream')
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
def streaming_get_function(req, local_path, **options):
|
|
287
|
+
''' Define the streaming version of the `get` method.
|
|
288
|
+
|
|
289
|
+
Returns the file content located at <local_path>.
|
|
290
|
+
|
|
291
|
+
Arguments:
|
|
292
|
+
req (flask.request):
|
|
293
|
+
The request instance given by Flask.
|
|
294
|
+
local_path (str):
|
|
295
|
+
The absolute path to the requested resource.
|
|
296
|
+
**options:
|
|
297
|
+
Arbitrary option arguments.
|
|
298
|
+
Currently no option is passed to this function.
|
|
299
|
+
|
|
300
|
+
Returns:
|
|
301
|
+
flask.Response:
|
|
302
|
+
A response message.
|
|
303
|
+
'''
|
|
304
|
+
bufsize = options.get('bufsize', 65535)
|
|
305
|
+
|
|
306
|
+
def is_active(FileStream):
|
|
307
|
+
b = FileStream.read(1)
|
|
308
|
+
FileStream.seek(-1, 1)
|
|
309
|
+
return bool(b)
|
|
310
|
+
|
|
311
|
+
def streaming():
|
|
312
|
+
with open(local_path, 'rb') as f:
|
|
313
|
+
while is_active(f):
|
|
314
|
+
yield f.read(bufsize)
|
|
315
|
+
|
|
316
|
+
return Response(streaming(), mimetype='application/octet-stream')
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
def get(req, target, **options):
|
|
320
|
+
''' Provide the `get` method.
|
|
321
|
+
|
|
322
|
+
This function is called when the `get` method is selected.
|
|
323
|
+
The <app.get_function> is called internally. When the customized
|
|
324
|
+
`get_function` is specified, the behavior of the `get` method
|
|
325
|
+
is overridden.
|
|
326
|
+
|
|
327
|
+
This method can be protected by setting `get_token`. The token is
|
|
328
|
+
reffered to as <app.get_token>. When <app.get_token> is defined,
|
|
329
|
+
the request must contain the `token` field.
|
|
330
|
+
In case that the `token` does not equal to <app.get_token>,
|
|
331
|
+
an error message will be returned.
|
|
332
|
+
|
|
333
|
+
Arguments:
|
|
334
|
+
req (flask.request):
|
|
335
|
+
A request instance generated by Flask.
|
|
336
|
+
target (str):
|
|
337
|
+
The path to the requested resource.
|
|
338
|
+
**options:
|
|
339
|
+
Arbitrary option arguments.
|
|
340
|
+
Currently no option is passed to this function.
|
|
341
|
+
|
|
342
|
+
Returns:
|
|
343
|
+
flask.Response:
|
|
344
|
+
A response message.
|
|
345
|
+
'''
|
|
346
|
+
local_path = '{}/{}'.format(app.workdir, target)
|
|
347
|
+
filename = os.path.basename(local_path)
|
|
348
|
+
emsg = lambda s: 'cannot access "{}": {{}}.\n'.format(target).format(s)
|
|
349
|
+
# check if a valid token is given.
|
|
350
|
+
if app.get_token is not None:
|
|
351
|
+
dprint('"get" function requres a token.')
|
|
352
|
+
token = req.form.get('token')
|
|
353
|
+
dprint('token = "{}"'.format(token))
|
|
354
|
+
if token != app.get_token:
|
|
355
|
+
return Response(emsg('invalid token'), status=400)
|
|
356
|
+
# assert path seems valid.
|
|
357
|
+
if invalid_path(target):
|
|
358
|
+
eprint('invalid path: {}'.format(target))
|
|
359
|
+
return Response(emsg('invalid path'), status=500)
|
|
360
|
+
# access to file.
|
|
361
|
+
try:
|
|
362
|
+
return app.get_function(req, local_path, **options)
|
|
363
|
+
except FileNotFoundError as e:
|
|
364
|
+
eprint(str(e))
|
|
365
|
+
return Response(emsg('file not found'), status=404)
|
|
366
|
+
except IsADirectoryError as e:
|
|
367
|
+
eprint(str(e))
|
|
368
|
+
return Response(emsg('cannot obtain directory'), status=400)
|
|
369
|
+
except Exception as e:
|
|
370
|
+
eprint(str(e))
|
|
371
|
+
errmsg = emsg(f'unexpected error: {str(e)} ({e.__class__.__name__})')
|
|
372
|
+
return Response(errmsg, status=500)
|
|
373
|
+
|
|
374
|
+
|
|
375
|
+
def default_dump_function(req, local_paths, **options):
|
|
376
|
+
''' Define the default behavior of the `dump` method.
|
|
377
|
+
|
|
378
|
+
Returns the file content located at <local_path>.
|
|
379
|
+
|
|
380
|
+
Arguments:
|
|
381
|
+
req (flask.request):
|
|
382
|
+
The request instance given by Flask.
|
|
383
|
+
local_paths (list):
|
|
384
|
+
The list of the absolute paths to the requested resources.
|
|
385
|
+
**options:
|
|
386
|
+
Arbitrary option arguments.
|
|
387
|
+
Currently no option is passed to this function.
|
|
388
|
+
|
|
389
|
+
Returns:
|
|
390
|
+
flask.Response:
|
|
391
|
+
A response message.
|
|
392
|
+
'''
|
|
393
|
+
def streaming():
|
|
394
|
+
buf = io.BytesIO()
|
|
395
|
+
with tarfile.open(fileobj=buf, mode='w') as arv:
|
|
396
|
+
for filename in local_paths:
|
|
397
|
+
pos = buf.tell()
|
|
398
|
+
basename = os.path.basename(filename)
|
|
399
|
+
arv.add(filename, arcname=basename)
|
|
400
|
+
buf.seek(pos)
|
|
401
|
+
yield buf.read()
|
|
402
|
+
# the stream position should be set something but zero.
|
|
403
|
+
# an init process is called when the stream position is zero.
|
|
404
|
+
buf.seek(1)
|
|
405
|
+
buf.truncate(1)
|
|
406
|
+
return Response(streaming(), mimetype='application/octet-stream')
|
|
407
|
+
|
|
408
|
+
|
|
409
|
+
def dump(req, filelist, **options):
|
|
410
|
+
''' Provide the `dump` method.
|
|
411
|
+
|
|
412
|
+
This function is called when the `dump` method is selected.
|
|
413
|
+
The <app.dump_function> is called internally. When the customized
|
|
414
|
+
`dump_function` is specified, the behavior of the `dump` method
|
|
415
|
+
is overridden.
|
|
416
|
+
|
|
417
|
+
This method can be protected by setting `get_token`. The token is
|
|
418
|
+
reffered to as <app.get_token>. When <app.get_token> is defined,
|
|
419
|
+
the request must contain the `token` field.
|
|
420
|
+
In case that the `token` does not equal to <app.get_token>,
|
|
421
|
+
an error message will be returned.
|
|
422
|
+
|
|
423
|
+
Arguments:
|
|
424
|
+
req (flask.request):
|
|
425
|
+
A request instance generated by Flask.
|
|
426
|
+
filelist (list):
|
|
427
|
+
The list of the paths to the requested resources.
|
|
428
|
+
**options:
|
|
429
|
+
Arbitrary option arguments.
|
|
430
|
+
Currently no option is passed to this function.
|
|
431
|
+
|
|
432
|
+
Returns:
|
|
433
|
+
flask.Response:
|
|
434
|
+
A response message.
|
|
435
|
+
'''
|
|
436
|
+
local_paths = ['{}/{}'.format(app.workdir, f) for f in filelist]
|
|
437
|
+
filenames = [os.path.basename(p) for p in local_paths]
|
|
438
|
+
emsg = lambda s: 'cannot access resources: {}.\n'.format(s)
|
|
439
|
+
# check if a valid token is given.
|
|
440
|
+
if app.get_token is not None:
|
|
441
|
+
dprint('"get" function requres a token.')
|
|
442
|
+
token = req.form.get('token')
|
|
443
|
+
dprint('token = "{}"'.format(token))
|
|
444
|
+
if token != app.get_token:
|
|
445
|
+
return Response(emsg('invalid token'), status=400)
|
|
446
|
+
# assert path seems valid.
|
|
447
|
+
for target in filelist:
|
|
448
|
+
if invalid_path(target):
|
|
449
|
+
eprint('invalid path: {}'.format(target))
|
|
450
|
+
return Response(emsg('invalid path'), status=500)
|
|
451
|
+
# access to file.
|
|
452
|
+
try:
|
|
453
|
+
return app.dump_function(req, local_paths, **options)
|
|
454
|
+
except FileNotFoundError as e:
|
|
455
|
+
eprint(str(e))
|
|
456
|
+
return Response(emsg('file not found'), status=404)
|
|
457
|
+
except IsADirectoryError as e:
|
|
458
|
+
eprint(str(e))
|
|
459
|
+
return Response(emsg('cannot obtain directory'), status=400)
|
|
460
|
+
except Exception as e:
|
|
461
|
+
eprint(str(e))
|
|
462
|
+
errmsg = emsg(f'unexpected error: {str(e)} ({e.__class__.__name__})')
|
|
463
|
+
return Response(errmsg, status=500)
|
|
464
|
+
|
|
465
|
+
|
|
466
|
+
def default_ping_function(req, local_path, **options):
|
|
467
|
+
''' Define the default behavior of the `ping` method.
|
|
468
|
+
|
|
469
|
+
Returns True if the the requested file exists at <local_path>.
|
|
470
|
+
|
|
471
|
+
Arguments:
|
|
472
|
+
req (flask.request):
|
|
473
|
+
The request instance given by Flask.
|
|
474
|
+
local_path (str):
|
|
475
|
+
The absolute paths to the requested resource.
|
|
476
|
+
**options:
|
|
477
|
+
Arbitrary option arguments.
|
|
478
|
+
Currently no option is passed to this function.
|
|
479
|
+
|
|
480
|
+
Returns:
|
|
481
|
+
flask.Response:
|
|
482
|
+
A response message.
|
|
483
|
+
'''
|
|
484
|
+
if os.path.exists(local_path):
|
|
485
|
+
return Response('', 200)
|
|
486
|
+
else:
|
|
487
|
+
raise FileNotFoundError(local_path)
|
|
488
|
+
|
|
489
|
+
|
|
490
|
+
def ping(req, target, **options):
|
|
491
|
+
''' Provide the `ping` method.
|
|
492
|
+
|
|
493
|
+
This function is called when the `ping` method is selected.
|
|
494
|
+
The <app.ping_function> is called internally. When the customized
|
|
495
|
+
`ping_function` is specified, the behavior of the `ping` method
|
|
496
|
+
is overridden.
|
|
497
|
+
|
|
498
|
+
This method can be protected by setting `get_token`. The token is
|
|
499
|
+
reffered to as <app.get_token>. When <app.get_token> is defined,
|
|
500
|
+
the request must contain the `token` field.
|
|
501
|
+
In case that the `token` does not equal to <app.get_token>,
|
|
502
|
+
an error message will be returned.
|
|
503
|
+
|
|
504
|
+
Arguments:
|
|
505
|
+
req (flask.request):
|
|
506
|
+
A request instance generated by Flask.
|
|
507
|
+
target (str):
|
|
508
|
+
The path to the requested resource.
|
|
509
|
+
**options:
|
|
510
|
+
Arbitrary option arguments.
|
|
511
|
+
Currently no option is passed to this function.
|
|
512
|
+
|
|
513
|
+
Returns:
|
|
514
|
+
flask.Response:
|
|
515
|
+
A response message.
|
|
516
|
+
'''
|
|
517
|
+
local_path = '{}/{}'.format(app.workdir, target)
|
|
518
|
+
filename = os.path.basename(local_path)
|
|
519
|
+
emsg = lambda s: 'cannot access "{}": {{}}.\n'.format(target).format(s)
|
|
520
|
+
# check if a valid token is given.
|
|
521
|
+
if app.get_token is not None:
|
|
522
|
+
dprint('"get" function requres a token.')
|
|
523
|
+
token = req.form.get('token')
|
|
524
|
+
dprint('token = "{}"'.format(token))
|
|
525
|
+
if token != app.get_token:
|
|
526
|
+
return Response(emsg('invalid token'), status=400)
|
|
527
|
+
# assert path seems valid.
|
|
528
|
+
if invalid_path(target):
|
|
529
|
+
eprint('invalid path: {}'.format(target))
|
|
530
|
+
return Response(emsg('invalid path'), status=500)
|
|
531
|
+
# access to file.
|
|
532
|
+
try:
|
|
533
|
+
return app.ping_function(req, local_path, **options)
|
|
534
|
+
except FileNotFoundError as e:
|
|
535
|
+
eprint(str(e))
|
|
536
|
+
return Response(emsg('file/directory not found'), status=404)
|
|
537
|
+
except Exception as e:
|
|
538
|
+
eprint(str(e))
|
|
539
|
+
errmsg = emsg(f'unexpected error: {str(e)} ({e.__class__.__name__})')
|
|
540
|
+
return Response(errmsg, status=500)
|
|
541
|
+
|
|
542
|
+
|
|
543
|
+
@app.route('/<path:target>', methods=['GET', ])
|
|
544
|
+
def access_by_get(target):
|
|
545
|
+
''' The access point of the SIDEX server.
|
|
546
|
+
|
|
547
|
+
This function handles the GET request to the SIDEX server.
|
|
548
|
+
This works only if the `token` is not specified.
|
|
549
|
+
|
|
550
|
+
Arguments:
|
|
551
|
+
target (str):
|
|
552
|
+
The path to the requested resource.
|
|
553
|
+
|
|
554
|
+
Returns:
|
|
555
|
+
flask.Response:
|
|
556
|
+
The response instance from the requested method.
|
|
557
|
+
'''
|
|
558
|
+
return get(request, target)
|
|
559
|
+
|
|
560
|
+
|
|
561
|
+
@app.route('/<path:target>', methods=['POST', ])
|
|
562
|
+
def access_get_by_post(target):
|
|
563
|
+
''' The access point of the SIDEX server.
|
|
564
|
+
|
|
565
|
+
This function handles the POST request to the SIDEX server.
|
|
566
|
+
The `method` field is mandatory. The value should be one of `get`,
|
|
567
|
+
`put` and `delete`. The corresponding function will be called.
|
|
568
|
+
|
|
569
|
+
The `token` field is required in the `put` and `delete` methods.
|
|
570
|
+
|
|
571
|
+
Arguments:
|
|
572
|
+
target (str):
|
|
573
|
+
The path to the requested resource.
|
|
574
|
+
|
|
575
|
+
Returns:
|
|
576
|
+
flask.Response:
|
|
577
|
+
The response instance from the requested method.
|
|
578
|
+
'''
|
|
579
|
+
method = request.form.get('method', 'none').lower()
|
|
580
|
+
if method == 'get':
|
|
581
|
+
return get(request, target)
|
|
582
|
+
elif method == 'put':
|
|
583
|
+
return put(request, target)
|
|
584
|
+
elif method == 'delete':
|
|
585
|
+
return delete(request, target)
|
|
586
|
+
elif method == 'ping':
|
|
587
|
+
return ping(request, target)
|
|
588
|
+
else:
|
|
589
|
+
return Response('invalid method: {}.\n'.format(method), status=400)
|
|
590
|
+
|
|
591
|
+
|
|
592
|
+
@app.route('/', methods=['GET', 'POST'])
|
|
593
|
+
def root():
|
|
594
|
+
if request.method == 'GET':
|
|
595
|
+
return Response('It works! The SIDEX server is running.\n')
|
|
596
|
+
else:
|
|
597
|
+
method = request.form.get('method', 'none').lower()
|
|
598
|
+
if method == 'dump':
|
|
599
|
+
filenames = request.form.getlist('filename')
|
|
600
|
+
return dump(request, filenames)
|
|
601
|
+
else:
|
|
602
|
+
return Response('invalid method: {}.\n'.format(method), status=400)
|
|
603
|
+
|
|
604
|
+
|
|
605
|
+
def setup(
|
|
606
|
+
workdir, subdir=None,
|
|
607
|
+
get_function=default_get_function,
|
|
608
|
+
put_function=default_put_function,
|
|
609
|
+
delete_function=default_delete_function,
|
|
610
|
+
dump_function=default_dump_function,
|
|
611
|
+
ping_function=default_ping_function,
|
|
612
|
+
get_token=None, put_token=None, delete_token=None,
|
|
613
|
+
log_handler=None, log_level='INFO'):
|
|
614
|
+
''' Setup a customized SIDEX server.
|
|
615
|
+
|
|
616
|
+
Arguments:
|
|
617
|
+
workdir (str):
|
|
618
|
+
The relative path to the working directory.
|
|
619
|
+
subdir (str,optional):
|
|
620
|
+
The name of the subdirectory.
|
|
621
|
+
get_function (function,optional):
|
|
622
|
+
The function instance called in the `get` method.
|
|
623
|
+
The following arguments are required:
|
|
624
|
+
- req (flask.request): The flask request instance.
|
|
625
|
+
- local_path (str): The local path to the resource.
|
|
626
|
+
put_function (function,optional):
|
|
627
|
+
The function instance called in the `put` method.
|
|
628
|
+
The following arguments are required:
|
|
629
|
+
- req (flask.request): The flask request instance.
|
|
630
|
+
- local_path (str): The local path to the resource.
|
|
631
|
+
delete_function (function,optional):
|
|
632
|
+
The function instance called in the `delete` method.
|
|
633
|
+
The following arguments are required:
|
|
634
|
+
- req (flask.request): The flask request instance.
|
|
635
|
+
- local_path (str): The local path to the resource.
|
|
636
|
+
dump_function (function,optional):
|
|
637
|
+
The function instance called in the `dump` method.
|
|
638
|
+
The following arguments are required:
|
|
639
|
+
- req (flask.request): The flask request instance.
|
|
640
|
+
- local_paths (list): The path list to the resources.
|
|
641
|
+
ping_function (function,optional):
|
|
642
|
+
The function instance called in the `ping` method.
|
|
643
|
+
The following arguments are required:
|
|
644
|
+
- req (flask,request): The flask request instance.
|
|
645
|
+
- local_path (str): The local path to the resource.
|
|
646
|
+
get_token (str,optional):
|
|
647
|
+
The secret token for the `get` method.
|
|
648
|
+
put_token (str,optional):
|
|
649
|
+
The secret token for the `put` method.
|
|
650
|
+
delete_token (str,optional):
|
|
651
|
+
The secret token for the `delete` method.
|
|
652
|
+
log_handler (logger.Logger,optional):
|
|
653
|
+
The user-defined log handler to override the default handler.
|
|
654
|
+
log_level (str,optional):
|
|
655
|
+
The log_level. `INFO` is given by default.
|
|
656
|
+
'''
|
|
657
|
+
if get_token is not None: assert len(get_token) > 0
|
|
658
|
+
if put_token is not None: assert len(put_token) > 0
|
|
659
|
+
if delete_token is not None: assert len(delete_token) > 0
|
|
660
|
+
app.workdir = workdir
|
|
661
|
+
app.get_function = get_function
|
|
662
|
+
app.put_function = put_function
|
|
663
|
+
app.delete_function = delete_function
|
|
664
|
+
app.dump_function = dump_function
|
|
665
|
+
app.ping_function = ping_function
|
|
666
|
+
app.get_token = get_token
|
|
667
|
+
app.put_token = put_token
|
|
668
|
+
app.delete_token = delete_token
|
|
669
|
+
app.subdir = subdir
|
|
670
|
+
app.logger.setLevel(log_level)
|
|
671
|
+
if log_handler is not None:
|
|
672
|
+
werkzeug.handlers = []
|
|
673
|
+
app.logger.handlers = [log_handler, ]
|
|
674
|
+
return app
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: sidex
|
|
3
|
+
Version: 1.5.1
|
|
4
|
+
Summary: SIDEX: Simple Data Exchange server over HTTP
|
|
5
|
+
Author-email: Ryou Ohsawa <ryou.ohsawa@nao.ac.jp>
|
|
6
|
+
Maintainer-email: Ryou Ohsawa <ryou.ohsawa@nao.ac.jp>
|
|
7
|
+
License-Expression: MIT
|
|
8
|
+
Project-URL: Homepage, https://github.com/astronasutarou/sidex
|
|
9
|
+
Classifier: Development Status :: 3 - Alpha
|
|
10
|
+
Classifier: Environment :: Console
|
|
11
|
+
Classifier: Intended Audience :: Science/Research
|
|
12
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Requires-Python: >=3.9
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
Requires-Dist: flask>=2.0
|
|
21
|
+
Requires-Dist: requests>=2.27
|
|
22
|
+
|
|
23
|
+
[](https://opensource.org/licenses/MIT)
|
|
24
|
+
[](https://github.com/astronasutarou/sidex/actions/workflows/github-code-scanning/codeql)
|
|
25
|
+
[](https://sidex.readthedocs.io/en/latest/?badge=latest)
|
|
26
|
+
|
|
27
|
+
# Simple Data Exchange server over HTTP
|
|
28
|
+
|
|
29
|
+
## Overview
|
|
30
|
+
This package provides a function to launch a simple file server. Getting, putting, and deleting files on the server via the HTTP POST methods are available. The function `setup_sidex()` returns a `flask` instance. You can launch a simple file server by `run()`.
|
|
31
|
+
|
|
32
|
+
``` python
|
|
33
|
+
from sidex import setup_sidex
|
|
34
|
+
|
|
35
|
+
target = '/path/to/directory'
|
|
36
|
+
app = setup_sidex(target)
|
|
37
|
+
app.run()
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Otherwise, you can directly call `sidex.server`.
|
|
41
|
+
|
|
42
|
+
``` sh
|
|
43
|
+
$ python -m sidex.server /path/to/directory
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
By default, only retrieving files (`get`) is available. The `put` and `delete` methods are enabled by setting a 'token' for each method. Of course, the `get` function can be restricted by a `token`.
|
|
47
|
+
|
|
48
|
+
The HTTP POST method is available to submit a request. Any request should contain the `method` field, which should be one of `get`, `put`, and `delete`. The `token` field may be required in some cases. The followings are samples with `curl`.
|
|
49
|
+
|
|
50
|
+
``` sh
|
|
51
|
+
$ curl http://0.0.0.0:8080/path/to/file -F 'method=get'
|
|
52
|
+
$ curl http://0.0.0.0:8080/path/to/upload -F 'method=put' -F 'payload=@filename' -F 'token=foo'
|
|
53
|
+
$ curl http://0.0.0.0:8080/path/to/delete -F 'method=delete' -F 'token=bar'
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
The package provides a function, `sidex_request()`, which is a wrapper function of `requests.post()`. You can directly execute `sidex.client`.
|
|
57
|
+
|
|
58
|
+
``` sh
|
|
59
|
+
$ python -m sidex.client http://0.0.0.0:8080/path/to/file
|
|
60
|
+
$ python -m sidex.client http://0.0.0.0:8080/path/to/file --ping
|
|
61
|
+
$ python -m sidex.client http://0.0.0.0:8080/path/to/upload -f upload_file
|
|
62
|
+
$ python -m sidex.client http://0.0.0.0:8080/path/to/delete -d
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
## Dependencies
|
|
67
|
+
The library is developed on Python 3.9.9. The following packages are required:
|
|
68
|
+
|
|
69
|
+
``` python
|
|
70
|
+
flask>=2.0
|
|
71
|
+
requests>=2.27
|
|
72
|
+
```
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
sidex/__init__.py,sha256=FHqXfZ3IwQBfmTzcI_3NdVUzPgQJJ8smrsDRUmHvTQc,1205
|
|
2
|
+
sidex/client.py,sha256=aM34SdBhXUf7e7glkuaFpJj8LlD29O1Iu5oLhJm9Y84,2228
|
|
3
|
+
sidex/dump.py,sha256=cHT5-ghfrImOU6ZM1U5m002OQIFl7_6CUDQJVYceENM,2782
|
|
4
|
+
sidex/request.py,sha256=6RXQg3ldZZqOOzyS4agbtI0sOcz2fAvBMnYoBj-staU,1680
|
|
5
|
+
sidex/server.py,sha256=IoG8oHS7xGRCRlZ6McAC7GWxeFTAGo3Uyii7VU1vqYk,1916
|
|
6
|
+
sidex/setup.py,sha256=SEDinX8t3W2PZjF7tdMZ9kkqeRRhtb8jLlk-uu7R9Z4,23511
|
|
7
|
+
sidex-1.5.1.dist-info/METADATA,sha256=2xOF5oGCf3qOYqyRMT1AFtEEjPEmC4X2qELhCIMkzr0,3059
|
|
8
|
+
sidex-1.5.1.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
9
|
+
sidex-1.5.1.dist-info/top_level.txt,sha256=9gw4MnFmjW8UkjL-AZPLbc-9jAuNPFxn9bc_guBVPwE,17
|
|
10
|
+
sidex-1.5.1.dist-info/RECORD,,
|