summaryrefslogtreecommitdiff
path: root/wsgi_server.py
blob: f754b149cd9c8dc0d50b51edac679edb920f1c0a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
#!/usr/bin/python3
# coding=utf-8

# TODO: this module is deprecated, remove it
# TODO: all new development should occur in server/handlers.py instead

import falcon
import json
import client
from errors.session import NoSuchSession, AuthenticationFailed

MAX_REQUEST_LENGTH = 1024 * 1024  # 1 MB

api = application = falcon.API()


def get_json_payload(req, session_is_optional=False):
    content_type_parts = req.content_type.split(';')
    content_type = content_type_parts[0].strip()
    encoding = 'utf-8'
    if len(content_type_parts) > 1:
        for part in content_type_parts[1:]:
            subparts = part.strip().split('=')
            if (len(subparts) == 2) and subparts[0] == 'charset':
                encoding = subparts[1]
    if not ((content_type == 'application/json') or (content_type == 'text/json')):
        raise falcon.HTTPUnsupportedMediaType('Unsupported content-type: {0}'.format(req.content_type))
    length = req.get_header('Content-Length')
    if length is None:
        raise falcon.HTTPLengthRequired('Requests without content-length are not accepted')
    try:
        l = int(length)
    except:
        raise falcon.HTTPBadRequest('Invalid Content-Length', 'The given Content-Length is not a number: {0}'.format(length))
    if l > MAX_REQUEST_LENGTH:
        raise falcon.HTTPError(falcon.HTTP_413, 'Request Entity Too Large', 'Cannot accept the request of length {0}: maximum allowed content-length is {1}'.format(length, MAX_REQUEST_LENGTH))
    try:
        txt = req.stream.read()
    except Exception as e:
        raise falcon.HTTPBadRequest('Error reading request', 'Error while reading the request body: {0}'.format(e.message))
    try:
        js = json.loads(txt.decode(encoding=encoding, errors='replace'))
    except ValueError as e:
        raise falcon.HTTPBadRequest('Error parsing JSON payload', 'Error while parsing the request as JSON: {0}'.format(e.message))
    sid = js.get('sid')
    if sid is None:
        if session_is_optional:
            return js, None
        raise falcon.HTTPBadRequest('No Session', 'Request is missing a session-ID')
    del js['sid']
    try:
        session = client.get_session_by_id(sid)
    except NoSuchSession:
        if session_is_optional:
            return js, None
        raise falcon.HTTPBadRequest('Session Expired', 'This user session has expired. Please log-in again.')
    return js, session


class CodeqService(object):
    """Base class for all CodeQ services.
     It only support the POST method and JSON data.
     Handles JSON decoding and encoding, and session retrieval which can be optional.
    """
    session_is_optional = False

    def on_post(self, req, resp):
        js, session = get_json_payload(req, self.session_is_optional)
        resp.body = json.dumps(self.process(js, session))
        resp.cache_control = 'private, no-cache, no-store'
        resp.content_type = 'application/json'
        resp.status = falcon.HTTP_200

    def process(self, js, session):
        raise falcon.HTTPNotFound()

class ProblemList(CodeqService):
    session_is_optional = True

    def process(self, js, session):
        return {'code': 0, 'message': 'ok', 'problems': client.list_problems()}

class Login(CodeqService):
    session_is_optional = True

    def process(self, js, old_session):
        username = js.get('username')
        password = js.get('password')
        if username is None:
            return {'code': 1, 'message': 'Username was not provided'}
        elif password is None:
            return {'code': 2, 'message': 'Password was not provided'}
        else:
            try:
                session = client.authenticate_and_create_session(username, password)
            except AuthenticationFailed:
                return {'code': 3, 'message': 'Username or password do not match'}
            else:
                if old_session:
                    old_session.destroy()
                return {'code': 0, 'message': 'OK', 'sid':session.get_sid()}

class Activity(CodeqService):
    def process(self, js, session):
        trace = js.get('trace')
        solution = js.get('solution')
        problem_id = js.get('problem_id')
        if (trace is not None) or (solution is not None):
            # we have something to do
            if problem_id is None:
                return {'code': 1, 'message': 'Problem ID is missing'}
            else:
                session.update_solution(problem_id, trace, solution)

class Query(CodeqService):
    def process(self, js, session):
        step = js.get('step')
        if step is None:
            return {'code': 1, 'message': '"step" is not set'}
        else:
            problem_id = js.get('problem_id')
            if problem_id is None:
                return {'code': 4, 'message': 'Problem ID not given'}
            trace = js.get('trace')
            prolog = session.get_prolog()
            program = None
            if step == 'run':
                program = js.get('program')
                query = js.get('query')
                if program is None:
                    result = {'code': 2, 'message': 'No program specified'}
                elif query is None:
                    result = {'code': 3, 'message': 'No query specified'}
                else:
                    messages, status, have_more = prolog.run_for_user(session.get_uid(), problem_id, program, query)
                    result = {'code': 0, 'message': 'ok', 'terminal': {'messages': messages, 'status': status, 'have_more': have_more}}
            elif step == 'next':
                messages, status, have_more = prolog.step()
                result = {'code': 0, 'message': 'ok', 'terminal': {'messages': messages, 'status': status, 'have_more': have_more}}
            elif step == 'end':
                messages, status, have_more = prolog.end()
                result = {'code': 0, 'message': 'ok', 'terminal': {'messages': messages, 'status': status, 'have_more': have_more}}
            else:
                result = {'code': 5, 'message': 'Unknown prolog step: {0}'.format(step)}
            if program or trace:
                session.update_solution(problem_id, trace, program)
            return result

# Pull stdout/stderr from the session's Python interpreter.
class PythonPull(CodeqService):
    def process(self, js, session):
        python = session.get_python()
        output = python.pull()
        return {'code': 0, 'message': 'ok', 'terminal': {'text': output if output else ''}}

# Push stdin to the session's Python interpreter.
class PythonPush(CodeqService):
    def process(self, js, session):
        text = js.get('text')
        if text is None:
            return {'code': 1, 'message': 'No input specified'}

        python = session.get_python()
        python.push(text)
        return {'code': 0, 'message': 'ok'}

class Hint(CodeqService):
    def process(self, js, session):
        language = js.get('language')
        problem_id = js.get('problem_id')
        program = js.get('program')

        if problem_id is None:
            return {'code': 1, 'message': 'No problem ID specified'}
        if program is None:
            return {'code': 2, 'message': 'No program specified'}

        if language == 'prolog':
            lang_session = session.get_prolog()
        elif language == 'python':
            lang_session = session.get_python()
        else:
            return {'code': 3, 'message': 'Unknown language specified'}

        hints = lang_session.hint(session.get_sid(), problem_id, program)
        return {'code': 0, 'message': 'ok', 'hints': hints}

class Test(CodeqService):
    def process(self, js, session):
        language = js.get('language')
        problem_id = js.get('problem_id')
        program = js.get('program')

        if problem_id is None:
            return {'code': 1, 'message': 'No problem ID specified'}
        if program is None:
            return {'code': 2, 'message': 'No program specified'}

        if language == 'prolog':
            lang_session = session.get_prolog()
        elif language == 'python':
            lang_session = session.get_python()
        else:
            return {'code': 3, 'message': 'Unknown language specified'}

        hints = lang_session.test(session.get_sid(), problem_id, program)
        return {'code': 0, 'message': 'ok', 'hints': hints}

class GetProblem(CodeqService):
    def process(self, js, session):
        language = js.get('language')
        problem_group = js.get('problem_group')
        problem = js.get('problem')
        if language is None:
            return {'code': 1, 'message': 'Language identifier not given'}
        elif problem_group is None:
            return {'code': 2, 'message': 'Problem group identifier not given'}
        elif problem is None:
            return {'code': 3, 'message': 'Problem identifier not given'}
        else:
            return {'code': 0, 'message': 'ok', 'data': session.get_problem_data(language, problem_group, problem)}

problem_list = ProblemList()
api.add_route('/list_problems', problem_list)

login = Login()
api.add_route('/login', login)

activity = Activity()
api.add_route('/activity', activity)

query = Query()
api.add_route('/query', query)

api.add_route('/python_pull', PythonPull())
api.add_route('/python_push', PythonPush())

hint = Hint()
api.add_route('/hint', hint)

test = Test()
api.add_route('/test', test)

get_problem = GetProblem()
api.add_route('/get_problem', get_problem)

if __name__ == '__main__':
    import logging
    from waitress import serve
    logger = logging.getLogger('waitress')
    logger.setLevel(logging.DEBUG)
    serve(application, host='0.0.0.0', port=8082)