Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a dmoj-rest server #319

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
125 changes: 125 additions & 0 deletions dmoj/api/api.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
---
swagger: "2.0"
info:
description: "A modern contest platform for the modern web.\n"
version: "1.0.0"
title: "DMOJ REST API"
contact:
email: "[email protected]"
license:
name: "GNU Affero General Public License v3.0"
url: "https://raw.githubusercontent.com/DMOJ/judge/master/LICENSE"
host: "localhost:8080"
basePath: "/simonvpe/dmoj/1.0.0"
tags:
- name: "submission"
description: "Problem Submission"
- name: "result"
description: "Submission Result"
schemes:
- "https"
- "http"
paths:
/submission:
post:
tags:
- "submission"
summary: "Submit a solution to the judge"
operationId: "add_submission"
consumes:
- "application/json"
produces:
- "application/json"
parameters:
- in: "body"
name: "body"
description: "Submission data"
required: true
schema:
$ref: "#/definitions/Submission"
responses:
200:
description: "successful operation"
schema:
$ref: "#/definitions/SubmissionResponse"
405:
description: "Invalid input"
x-swagger-router-controller: "dmoj.rest"
/submissionResult/{submissionId}:
get:
tags:
- "result"
summary: "Retrieve the result of a submission"
operationId: "submission_result_get"
produces:
- "application/json"
parameters:
- name: "submissionId"
in: "path"
description: "id of submission to return"
required: true
type: "integer"
format: "int64"
responses:
200:
description: "successful operation"
schema:
$ref: "#/definitions/SubmissionResult"
x-swagger-router-controller: "dmoj.rest"
definitions:
Submission:
type: "object"
required:
- "languageId"
- "problemId"
- "sourceCode"
properties:
problemId:
type: "string"
description: "id of problem to grade"
languageId:
type: "string"
description: "language of problem to grade"
sourceCode:
type: "string"
format: "base64"
description: "the source code to grade"
timeLimit:
type: "number"
description: "time limit for grading, in seconds"
memoryLimit:
type: "integer"
format: "int64"
description: "memory limit for grading, in kilobytes"
default: 65536
example:
timeLimit: 0.8008281904610115
sourceCode: "sourceCode"
languageId: "languageId"
memoryLimit: 6
problemId: "problemId"
SubmissionResponse:
type: "object"
required:
- "submissionId"
properties:
submissionId:
type: "integer"
format: "int64"
description: "id of the submission"
example:
submissionId: 0
SubmissionResult:
type: "object"
required:
- "submissionId"
properties:
submissionId:
type: "integer"
format: "int64"
description: "id of the submission"
example:
submissionId: 0
externalDocs:
description: "Find out more about Swagger"
url: "http://swagger.io"
150 changes: 150 additions & 0 deletions dmoj/rest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
import os, sys, connexion, six, logging
from dmoj import judgeenv, executors
from dmoj.judge import Judge
from flask import jsonify, current_app
from operator import itemgetter
from flask import g
from enum import Enum
from base64 import b64decode

class JudgeState(Enum):
FAILED = 0
SUCCESS = 1

class LocalPacketManager(object):
def __init__(self, judge):
self.judge = judge

def _receive_packet(self, packet):
pass

def supported_problems_packet(self, problems):
pass

def test_case_status_packet(self, position, result):
self.judge.graded_submissions[-1]['testCaseStatus'] = result.readable_codes()

def compile_error_packet(self, message):
self.judge.graded_submissions[-1]['compileError'].append(message)

def compile_message_packet(self, message):
self.judge.graded_submissions[-1]['compileMessage'].append(message)

def internal_error_packet(self, message):
self.judge.graded_submissions[-1]['internalError'].append(message)

def begin_grading_packet(self, is_pretested):
pass

def grading_end_packet(self):
pass

def batch_begin_packet(self):
pass

def batch_end_packet(self):
pass

def current_submission_packet(self):
pass

def submission_terminated_packet(self):
pass

def submission_acknowledged_packet(self, sub_id):
pass

def run(self):
pass

def close(self):
pass


class LocalJudge(Judge):
def __init__(self):
super(LocalJudge, self).__init__()
self.packet_manager = LocalPacketManager(self)
self.next_submission_id = 0
self.graded_submissions = []

def get_judge():
judge = getattr(g, '_judge', None)
if judge is None:
g._judge = LocalJudge()
return g._judge

# POST /submission
def add_submission(body):
judge = get_judge()
body = connexion.request.get_json()
problem_id = body['problemId']
language_id = body['languageId']
time_limit = body['timeLimit']
memory_limit = body['memoryLimit']
source = body['sourceCode']

if problem_id not in map(itemgetter(0), judgeenv.get_supported_problems()):
return jsonify({
'error': "unknown problem %s" % problem_id
}), 405

if language_id not in executors.executors:
return jsonify({'error': "unknown languae %s" % language_id}), 405

if time_limit <= 0:
return jsonify({'error': "timeLimit must be >= 0"}), 405

if memory_limit <= 0:
return jsonify({'error': "memoryLimit must be >= 0"}), 405

submission_id = judge.next_submission_id

judge.graded_submissions.append({
"submissionId": submission_id,
"problemId": problem_id,
"languageId": language_id,
"sourceCode": source,
"timeLimit": time_limit,
"memoryLimit": memory_limit,
"compileError": [],
"compileMessage": [],
"testCaseResults": [],
"internalError":[]
})

source = b64decode(source).decode('utf-8')
print(source)
judge.begin_grading(submission_id, problem_id, language_id, source, time_limit,
memory_limit, False, False, blocking=True)

judge.next_submission_id += 1

return jsonify(judge.graded_submissions[submission_id]), 200

# GET /submissionResult/{submissionId}
def submission_result_get(submissionId):
judge = get_judge()
return jsonify(judge.graded_submissions[submission_id]), 200

def main():
judgeenv.load_env(cli=True)
executors.load_executors()

logging.basicConfig(filename=judgeenv.log_file, level=logging.INFO,
format='%(levelname)s %(asctime)s %(module)s %(message)s')

for warning in judgeenv.startup_warnings:
print(ansi_style('#ansi[Warning: %s](yellow)' % warning))
del judgeenv.startup_warnings
print()

server = connexion.FlaskApp(__name__, specification_dir='api/')
with server.app.app_context():
judge = get_judge()
judge.listen()
server.add_api('api.yaml')
server.run(port=8080)

if __name__ == '__main__':
main()
4 changes: 3 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,16 +150,18 @@ def unavailable(self, e):
'dmoj.cptbox': ['syscalls/aliases.list', 'syscalls/*.tbl'],
'dmoj.executors': ['csbox.exe', 'java-sandbox.jar', '*.policy'],
'dmoj.wbox': ['getaddr*.exe', 'dmsec*.dll'],
'': ['api/api.yaml']
},
entry_points={
'console_scripts': [
'dmoj = dmoj.judge:main',
'dmoj-cli = dmoj.cli:main',
'dmoj-autoconf = dmoj.executors.autoconfig:main',
'dmoj-rest = dmoj.rest:main'
],
},
ext_modules=cythonize(extensions),
install_requires=['watchdog', 'pyyaml', 'ansi2html', 'termcolor', 'pygments', 'six', 'setproctitle'],
install_requires=['watchdog', 'pyyaml', 'ansi2html', 'termcolor', 'pygments', 'six', 'setproctitle', 'connexion'],
tests_require=['mock', 'requests'],
extras_require={
'test': ['mock'],
Expand Down