Merge pull request #23 from ty666/master

Add JudgeServer client for go
This commit is contained in:
李扬 2018-12-16 20:32:35 +08:00 committed by GitHub
commit e30664bb5e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 720 additions and 0 deletions

19
client/go/README.md Normal file
View File

@ -0,0 +1,19 @@
# JudgeServer client for Golang
# Installation
Install:
```shell
go get -d github.com/QingdaoU/JudgeServer/client/go
```
Import:
```go
import "github.com/QingdaoU/JudgeServer/client/go"
```
# Examples
[examples](https://github.com/QingdaoU/JudgeServer/tree/master/client/go/examples)

184
client/go/client.go Normal file
View File

@ -0,0 +1,184 @@
package judge
import (
"net/http"
"io/ioutil"
"io"
"encoding/json"
"time"
"bytes"
"fmt"
)
// Data 成员的类型参考 https://github.com/QingdaoU/Judger/blob/b6414e7a6715eb013b1ffeb7cfb04626a3ff5b4e/src/runner.h#L73
type Data struct {
CpuTime int
Result int
Memory int64 // https://github.com/QingdaoU/Judger/blob/b6414e7a6715eb013b1ffeb7cfb04626a3ff5b4e/src/runner.h#L76
RealTime int
Signal int
Error int
ExitCode int
OutputMd5 string
Output interface{}
TestCase string
}
type Resp struct {
RespData interface{} `json:"data"`
RespErr string `json:"err"`
err error `json:"-"`
}
func (resp *Resp) Data() interface{} {
if resp == nil {
return nil
}
return resp.RespData
}
func (resp *Resp) StringData() string {
if resp == nil {
return ""
}
str, _ := resp.RespData.(string)
return str
}
func (resp *Resp) SliceData() []*Data {
if resp == nil {
return nil
}
slice, _ := resp.RespData.([]interface{})
data := make([]*Data, 0, len(slice))
for _, s := range slice {
item, ok := s.(map[string]interface{})
if ok {
cpuTimeF64, _ := item["cpu_time"].(float64)
resultF64, _ := item["result"].(float64)
memoryF64, _ := item["memory"].(float64)
realTimeF64, _ := item["real_time"].(float64)
signalF64, _ := item["signal"].(float64)
errorF64, _ := item["error"].(float64)
exitCodeF64, _ := item["exit_code"].(float64)
outputMd5, _ := item["output_md5"].(string)
testCase, _ := item["test_case"].(string)
data = append(data, &Data{
CpuTime: int(cpuTimeF64),
Result: int(resultF64),
Memory: int64(memoryF64),
RealTime: int(realTimeF64),
Signal: int(signalF64),
Error: int(errorF64),
ExitCode: int(exitCodeF64),
OutputMd5: outputMd5,
Output: item["output"],
TestCase: testCase,
})
}
}
return data
}
func (resp *Resp) Err() error {
if resp == nil {
return nil
}
return resp.err
}
func parseResp(body []byte) (*Resp, error) {
resp := &Resp{}
err := json.Unmarshal(body, resp)
if err != nil {
return nil, err
}
// 有错误的响应了
if resp.RespErr != "" {
resp.err = fmt.Errorf("err: %s, data: %s", resp.RespErr, resp.RespData)
}
return resp, nil
}
type Client struct {
opts *options
httpClient *http.Client
}
func (c *Client) request(method, path string, body io.Reader) (resp *Resp, err error) {
req, err := http.NewRequest("POST", c.opts.EndpointURL+"/"+path, body)
if err != nil {
return
}
req.Header.Set("X-Judge-Server-Token", c.opts.sha256Token)
req.Header.Set("Content-Type", "application/json")
httpResp, err := c.httpClient.Do(req)
if err != nil {
return
}
b, err := ioutil.ReadAll(httpResp.Body)
if err != nil {
return
}
httpResp.Body.Close()
return parseResp(b)
}
func (c *Client) post(path string, body io.Reader) (resp *Resp, err error) {
return c.request("POST", path, body)
}
// Ping Judge server
func (c *Client) Ping() (resp *Resp, err error) {
resp, err = c.post("ping", nil)
return
}
func (c *Client) CompileSpj(src, spjVersion string, spjCompileConfig *CompileConfig) (resp *Resp, err error) {
data := map[string]interface{}{
"src": src,
"spj_version": spjVersion,
"spj_compile_config": spjCompileConfig,
}
b, err := json.Marshal(data)
if err != nil {
return
}
resp, err = c.post("compile_spj", bytes.NewReader(b))
return
}
func New(endpointURL, token string, timeout time.Duration) *Client {
return NewClient(
WithEndpointURL(endpointURL),
WithToken(token),
WithTimeout(timeout),
)
}
func (c *Client) SetOptions(options ...Option) {
originTimeout := c.opts.Timeout
for _, o := range options {
o(c.opts)
}
if c.opts.Timeout != originTimeout {
c.httpClient.Timeout = c.opts.Timeout
}
}
func NewClient(options ...Option) *Client {
opts := DefaultOptions
for _, o := range options {
o(opts)
}
return &Client{
opts: opts,
httpClient: &http.Client{
Timeout: opts.Timeout,
},
}
}

56
client/go/client_test.go Normal file
View File

@ -0,0 +1,56 @@
package judge
import (
"testing"
)
var client *Client
func TestMain(m *testing.M) {
// 创建一个client。 这句代码等价于 New("http://127.0.0.1:12358", "YOUR_TOKEN_HERE", 0)
client = NewClient(
WithEndpointURL("http://127.0.0.1:12358"),
WithToken("YOUR_TOKEN_HERE"),
WithTimeout(0),
)
m.Run()
}
func TestClient_Ping(t *testing.T) {
resp, err := client.Ping()
if err != nil {
t.Errorf("unexpected error. error: %+v", err)
return
}
if resp.Err() != nil {
t.Errorf("unexpected error. error: %+v", resp.Err())
return
}
if resp.RespData == nil {
t.Error("resp.RespData unexpected nil")
return
}
}
func TestClient_CompileSpj(t *testing.T) {
cSpjSrc := `
#include <stdio.h>
int main(){
return 1;
}
`
resp, err := client.CompileSpj(cSpjSrc, "2", CLangSPJCompile)
if err != nil {
t.Errorf("unexpected error. error: %+v", err)
return
}
if resp.Err() != nil {
t.Errorf("unexpected error. error: %+v", resp.Err())
return
}
if resp.RespData == nil {
t.Error("resp.RespData unexpected nil")
return
}
}

View File

@ -0,0 +1,168 @@
package main
import (
"github.com/QingdaoU/JudgeServer/client/go"
"fmt"
)
var (
cSrc = `
#include <stdio.h>
int main(){
int a, b;
scanf("%d%d", &a, &b);
printf("%d\n", a+b);
return 0;
}
`
cSPJSrc = `
#include <stdio.h>
int main(){
return 1;
}
`
cppSrc = `
#include <iostream>
using namespace std;
int main()
{
int a,b;
cin >> a >> b;
cout << a+b << endl;
return 0;
}
`
javaSrc = `
import java.util.Scanner;
public class Main{
public static void main(String[] args){
Scanner in=new Scanner(System.in);
int a=in.nextInt();
int b=in.nextInt();
System.out.println(a + b);
}
}
`
py2Src = `
s = raw_input()
s1 = s.split(" ")
print int(s1[0]) + int(s1[1])
`
py3Src = `
s = input()
s1 = s.split(" ")
print(int(s1[0]) + int(s1[1]))
`
)
func main() {
// 创建一个client。 这句代码等价于
// 1.
//client := judge.NewClient(
// judge.WithEndpointURL("http://127.0.0.1:12358"),
// judge.WithToken("YOUR_TOKEN_HERE"),
// judge.WithTimeout(0),
//)
// 2.
// client := judge.New("http://127.0.0.1:12358", "YOUR_TOKEN_HERE", 0)
// 3.
client := judge.NewClient(judge.WithTimeout(0))
client.SetOptions(judge.WithEndpointURL("http://127.0.0.1:12358"), judge.WithToken("YOUR_TOKEN_HERE"))
fmt.Println("ping:")
resp, err := client.Ping()
if err != nil {
// 这个 err 是发生在 client 这边的错误。 例如json编码失败
fmt.Printf("ping client error. error is: %v.\n", err)
} else if resp.Err() != nil {
// 这个 resp.Err() 是 JudgeServer 响应的错误。 例如token错误 TokenVerificationFailed
fmt.Printf("ping server error. error is: %v.\n", resp.Err().Error())
} else {
fmt.Println(resp.Data())
}
fmt.Println()
fmt.Println("cpp_judge")
resp, _ = client.JudgeWithRequest(&judge.JudgeRequest{
Src: cppSrc,
LanguageConfig: judge.CPPLangConfig,
MaxCpuTime: 1000,
MaxMemory: 128 * 1024 * 1024,
TestCaseId: "normal",
})
printSliceData(resp.SliceData())
fmt.Println()
fmt.Println("java_judge")
resp, _ = client.JudgeWithRequest(&judge.JudgeRequest{
Src: javaSrc,
LanguageConfig: judge.JavaLangConfig,
MaxCpuTime: 1000,
MaxMemory: 256 * 1024 * 1024,
TestCaseId: "normal",
})
printSliceData(resp.SliceData())
fmt.Println()
fmt.Println("c_spj_judge")
resp, _ = client.JudgeWithRequest(&judge.JudgeRequest{
Src: cSrc,
LanguageConfig: judge.CLangConfig,
MaxCpuTime: 1000,
MaxMemory: 128 * 1024 * 1024,
TestCaseId: "spj",
SPJVersion: "3",
SPJConfig: judge.CLangSPJConfig,
SPJCompileConfig: judge.CLangSPJCompile,
SPJSrc: cSPJSrc,
})
printSliceData(resp.SliceData())
fmt.Println()
fmt.Println("py2_judge")
resp, _ = client.JudgeWithRequest(&judge.JudgeRequest{
Src: py2Src,
LanguageConfig: judge.PY2LangConfig,
MaxCpuTime: 1000,
MaxMemory: 128 * 1024 * 1024,
TestCaseId: "normal",
})
printSliceData(resp.SliceData())
fmt.Println()
fmt.Println("py3_judge")
resp, _ = client.JudgeWithRequest(&judge.JudgeRequest{
Src: py3Src,
LanguageConfig: judge.PY3LangConfig,
MaxCpuTime: 1000,
MaxMemory: 128 * 1024 * 1024,
TestCaseId: "normal",
})
printSliceData(resp.SliceData())
fmt.Println()
// CompileError example
fmt.Println("CompileError example")
resp, err = client.JudgeWithRequest(&judge.JudgeRequest{
Src: "this bad code",
LanguageConfig: judge.JavaLangConfig,
MaxCpuTime: 1000,
MaxMemory: 256 * 1024 * 1024,
TestCaseId: "normal",
})
// fmt.Println(resp.RespErr) // "CompileError"
fmt.Println(resp.StringData()) // 错误信息
}
func printSliceData(slice []*judge.Data) {
fmt.Print("[\n")
for _, item := range slice {
fmt.Printf("\t%#v,\n", item)
}
fmt.Print("]\n")
}

46
client/go/judge.go Normal file
View File

@ -0,0 +1,46 @@
package judge
import (
"time"
"bytes"
"encoding/json"
)
type JudgeRequest struct {
Src string `json:"src"`
LanguageConfig *LangConfig `json:"language_config"`
MaxCpuTime int64 `json:"max_cpu_time"`
MaxMemory int64 `json:"max_memory"`
TestCaseId string `json:"test_case_id"`
SPJVersion string `json:"spj_version"`
SPJConfig *SPJConfig `json:"spj_config"`
SPJCompileConfig *CompileConfig `json:"spj_compile_config"`
SPJSrc string `json:"spj_src"`
Output bool `json:"output"`
}
// 这个方法为了模仿 php 和 python client 不推荐使用
func (c *Client) Judge(src string, languageConfig *LangConfig, maxCpuTime time.Duration, maxMemory int64, testCaseId,
spjVersion string, spjConfig *SPJConfig, spjCompileConfig *CompileConfig, spjSrc string, output bool) (resp *Resp, err error) {
return c.JudgeWithRequest(&JudgeRequest{
Src: src,
LanguageConfig: languageConfig,
MaxCpuTime: int64(maxCpuTime),
MaxMemory: maxMemory,
TestCaseId: testCaseId,
SPJVersion: spjVersion,
SPJConfig: spjConfig,
SPJCompileConfig: spjCompileConfig,
SPJSrc: spjSrc,
Output: output,
})
}
func (c *Client) JudgeWithRequest(req *JudgeRequest) (resp *Resp, err error) {
b, err := json.Marshal(req)
if err != nil {
return
}
resp, err = c.post("judge", bytes.NewReader(b))
return
}

76
client/go/judge_test.go Normal file
View File

@ -0,0 +1,76 @@
package judge
import "testing"
func TestClient_JudgeWithRequest(t *testing.T) {
javaSrc := `
import java.util.Scanner;
public class Main{
public static void main(String[] args){
Scanner in=new Scanner(System.in);
int a=in.nextInt();
int b=in.nextInt();
System.out.println(a + b);
}
}
`
cSrc := `
#include <stdio.h>
int main(){
int a, b;
scanf("%d%d", &a, &b);
printf("%d\n", a+b);
return 0;
}
`
cSPJSrc := `
#include <stdio.h>
int main(){
return 1;
}
`
var tests = []struct {
JudgeRequest *JudgeRequest
}{
{
JudgeRequest: &JudgeRequest{
Src: javaSrc,
LanguageConfig: JavaLangConfig,
MaxCpuTime: 1000,
MaxMemory: 256 * 1024 * 1024,
TestCaseId: "normal",
},
},
{
JudgeRequest: &JudgeRequest{
Src: cSrc,
LanguageConfig: CLangConfig,
MaxCpuTime: 1000,
MaxMemory: 128 * 1024 * 1024,
TestCaseId: "spj",
SPJVersion: "3",
SPJConfig: CLangSPJConfig,
SPJCompileConfig: CLangSPJCompile,
SPJSrc: cSPJSrc,
},
},
}
for _, test := range tests {
resp, err := client.JudgeWithRequest(test.JudgeRequest)
if err != nil {
t.Errorf("unexpected error. error: %+v", err)
return
}
if resp.Err() != nil {
t.Errorf("unexpected error. error: %+v", resp.Err())
return
}
if resp.RespData == nil {
t.Error("resp.RespData unexpected nil")
return
}
}
}

126
client/go/languages.go Normal file
View File

@ -0,0 +1,126 @@
package judge
type CompileConfig struct {
SrcName string `json:"src_name"`
ExeName string `json:"exe_name"`
MaxCpuTime int64 `json:"max_cpu_time"`
MaxRealTime int64 `json:"max_real_time"`
MaxMemory int64 `json:"max_memory"`
CompileCommand string `json:"compile_command"`
}
var DefaultEnv = []string{"LANG=en_US.UTF-8", "LANGUAGE=en_US:en", "LC_ALL=en_US.UTF-8"}
type RunConfig struct {
Command string `json:"command"`
SeccompRule string `json:"seccomp_rule"`
Env []string `json:"env"`
MemoryLimitCheckOnly int `json:"memory_limit_check_only"`
}
type LangConfig struct {
CompileConfig CompileConfig `json:"compile"`
RunConfig RunConfig `json:"run"`
}
type SPJConfig struct {
ExeName string `json:"exe_name"`
Command string `json:"command"`
SeccompRule string `json:"seccomp_rule"`
}
var CLangConfig = &LangConfig{
CompileConfig: CompileConfig{
SrcName: "main.c",
ExeName: "main",
MaxCpuTime: 3000,
MaxRealTime: 5000,
MaxMemory: 128 * 1024 * 1024,
CompileCommand: "/usr/bin/gcc -DONLINE_JUDGE -O2 -w -fmax-errors=3 -std=c99 {src_path} -lm -o {exe_path}",
},
RunConfig: RunConfig{
Command: "{exe_path}",
SeccompRule: "c_cpp",
Env: DefaultEnv,
},
}
var CLangSPJCompile = &CompileConfig{
SrcName: "spj-{spj_version}.c",
ExeName: "spj-{spj_version}",
MaxCpuTime: 3000,
MaxRealTime: 5000,
MaxMemory: 1024 * 1024 * 1024,
CompileCommand: "/usr/bin/gcc -DONLINE_JUDGE -O2 -w -fmax-errors=3 -std=c99 {src_path} -lm -o {exe_path}",
}
var CLangSPJConfig = &SPJConfig{
ExeName: "spj-{spj_version}",
Command: "{exe_path} {in_file_path} {user_out_file_path}",
SeccompRule: "c_cpp",
}
var CPPLangConfig = &LangConfig{
CompileConfig: CompileConfig{
SrcName: "main.cpp",
ExeName: "main",
MaxCpuTime: 3000,
MaxRealTime: 5000,
MaxMemory: 128 * 1024 * 1024,
CompileCommand: "/usr/bin/g++ -DONLINE_JUDGE -O2 -w -fmax-errors=3 -std=c++11 {src_path} -lm -o {exe_path}",
},
RunConfig: RunConfig{
Command: "{exe_path}",
SeccompRule: "c_cpp",
Env: DefaultEnv,
},
}
var JavaLangConfig = &LangConfig{
CompileConfig: CompileConfig{
SrcName: "Main.java",
ExeName: "Main",
MaxCpuTime: 3000,
MaxRealTime: 5000,
MaxMemory: -1,
CompileCommand: "/usr/bin/javac {src_path} -d {exe_dir} -encoding UTF8",
},
RunConfig: RunConfig{
Command: "/usr/bin/java -cp {exe_dir} -XX:MaxRAM={max_memory}k -Djava.security.manager -Dfile.encoding=UTF-8 -Djava.security.policy==/etc/java_policy -Djava.awt.headless=true Main",
SeccompRule: "",
Env: DefaultEnv,
MemoryLimitCheckOnly: 1,
},
}
var PY2LangConfig = &LangConfig{
CompileConfig: CompileConfig{
SrcName: "solution.py",
ExeName: "solution.pyc",
MaxCpuTime: 3000,
MaxRealTime: 5000,
MaxMemory: 128 * 1024 * 1024,
CompileCommand: "/usr/bin/python -m py_compile {src_path}",
},
RunConfig: RunConfig{
Command: "/usr/bin/python {exe_path}",
SeccompRule: "general",
Env: DefaultEnv,
},
}
var PY3LangConfig = &LangConfig{
CompileConfig: CompileConfig{
SrcName: "solution.py",
ExeName: "__pycache__/solution.cpython-35.pyc",
MaxCpuTime: 3000,
MaxRealTime: 5000,
MaxMemory: 128 * 1024 * 1024,
CompileCommand: "/usr/bin/python3 -m py_compile {src_path}",
},
RunConfig: RunConfig{
Command: "/usr/bin/python3 {exe_path}",
SeccompRule: "general",
Env: append(DefaultEnv, "PYTHONIOENCODING=UTF-8"),
},
}

45
client/go/options.go Normal file
View File

@ -0,0 +1,45 @@
package judge
import (
"time"
"crypto/sha256"
"encoding/hex"
)
type options struct {
// scheme://host:port .
EndpointURL string
Token string
sha256Token string
// 请求超时时间
// 如果 Timeout 为 0那么意味着不会超时
Timeout time.Duration
}
var DefaultOptions = &options{
EndpointURL: "http://127.0.0.1:12358",
Token: "YOUR_TOKEN_HERE",
Timeout: 10 * time.Second,
}
type Option func(*options)
func WithEndpointURL(u string) Option {
return func(o *options) {
o.EndpointURL = u
}
}
func WithToken(token string) Option {
return func(o *options) {
o.Token = token
sha256Token := sha256.Sum256([]byte(token))
o.sha256Token = hex.EncodeToString(sha256Token[:])
}
}
func WithTimeout(timeout time.Duration) Option {
return func(o *options) {
o.Timeout = timeout
}
}