Source code for ly.server.handler
# This file is part of python-ly, https://pypi.python.org/pypi/python-ly
#
# Copyright (c) 2014 - 2015 by Wilbert Berendsen
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
# See http://www.gnu.org/licenses/ for more information.
"""
HTTP request handler
"""
from __future__ import unicode_literals
try:
from BaseHTTPServer import BaseHTTPRequestHandler
except ImportError:
from http.server import BaseHTTPRequestHandler
import json
import copy
# Prototype (in JavaScript sense) from which each command copies its options
default_opts = None
[docs]class RequestHandler(BaseHTTPRequestHandler):
[docs] def create_command(self, cmd):
"""
Parse one command from the JSON data, plus optionally some commands to
set variables. Returns an array with command._command instances.
Raises exceptions upon faulty data.
"""
from . import command
result = []
# set variables before executing the command
if 'variables' in cmd:
for v in cmd['variables']:
result.append(command.set_variable(
v + "=" + cmd['variables'][v]))
# instantiate the command.
if not 'command' in cmd:
raise ValueError("Malformed JSON data in request body (missing 'command' field).\n" +
"Object:\n" + json.dumps(cmd))
cmd_name = cmd['command'].replace('-', '_')
if not cmd_name in command.known_commands:
raise ValueError("unknown command: " + cmd_name)
# add arguments to command if present.
args = cmd.get('args', '')
args = [args] if args else []
try:
result.append(getattr(command, cmd_name)(*args))
except TypeError as ae:
raise ValueError("Error creating command {cmd} with args {args}.\n{msg}".format(
cmd = cmd_name,
args = ", ".join(args),
msg = str(ae)))
return result
[docs] def process_options(self, opts):
"""
Instantiate a copy of the default options and
update with the given opts
"""
result = copy.deepcopy(default_opts)
for opt in opts:
# handle special case where option name doesn't match CL interface
if opt == 'language':
result.set_variable('default-language', opts[opt])
else:
result.set_variable(opt, opts[opt])
return result
[docs] def process_json_request(self, request):
"""
Configure the action(s) to be taken, based on the JSON object.
Raise errors when the JSON object can't be properly understood.
Run the commands and return a string (from cursor.text() ).
"""
# set up an Options object and
# override defaults with given options
opts = self.process_options(request.get('options', []))
# set up commands
commands = []
for c in request['commands']:
commands.extend(self.create_command(c))
# create document from passed data
import ly.document
doc = ly.document.Document(request['data'], opts.mode)
doc.filename = ""
# data structure for the results
data = {
'doc': {
'commands': [],
'content': ly.document.Cursor(doc)
},
'info': [],
'exports': []
}
# run commands, which modify data in-place
for c in commands:
c.run(opts, data)
data['doc']['content'] = data['doc']['content'].text()
return data
[docs] def read_json_request(self):
"""
Returns the message body parsed to a dictionary
from JSON data. Raises
- RuntimeWarning when no JSON data is present
- ValueError when JSON parsing fails
"""
content_len = int(self.headers['content-length'])
if content_len == 0:
#TODO: When testing is over remove (or comment out)
# the following two lines and raise the exception instead.
from . import testjson
return testjson.test_request
raise RuntimeWarning("No JSON data in request body")
req_body = self.rfile.read(content_len)
# Python2 has string, Python3 has bytestream
if not isinstance(req_body, str):
req_body = req_body.decode('utf-8')
# parse body, initial validation
try:
request = json.loads(req_body)
except Exception as e:
raise ValueError("Malformed JSON data in request body:\n" + str(e))
if not 'commands' in request:
raise ValueError("Malformed JSON request. Missing 'commands' property")
if not 'data' in request:
raise ValueError("Malformed JSON request. Missing 'data' property")
return request
########################
### Request handlers ###
########################
[docs] def do_POST(self):
"""
A POST request is expected to contain the task to be executed
as a JSON object in the request body.
The POST handler (currently) ignores the URL.
"""
try:
request = self.read_json_request()
result = self.process_json_request(request)
except Exception as e:
# TODO: should we disambiguate (ValueError, RuntimeWarning, others)?
# use HTML templates
self.send_error(400, format(e))
return
# Send successful response
self.send_response(200)
self.send_header('Content-type', 'application/json')
self.end_headers()
res_body = json.dumps(result)
if isinstance(res_body, str):
res_body = res_body.encode()
self.wfile.write(res_body)