├── .classpath
├── .gitignore
├── Dockerfile
├── LICENSE
├── README.md
├── code_segment.sql
├── config
├── db-default.properties
├── docker-default.properties
├── mybatis-config.xml
└── user-default.properties
├── pom.xml
├── runcodeNetty.iml
└── src
└── main
├── java
└── com
│ └── runcode
│ ├── ServerBootStrap.java
│ ├── controller
│ └── CodeSegmentController.java
│ ├── docker
│ ├── DockerJavaClient.java
│ └── RunCodeResultCallback.java
│ ├── entities
│ ├── CodeLang.java
│ ├── CodeSegmentPO.java
│ ├── CodeWrapperDTO.java
│ ├── Result.java
│ └── SaveCodeRequest.java
│ ├── mapper
│ └── CodeSegmentMapper.java
│ ├── server
│ ├── http
│ │ ├── RestApiServer.java
│ │ ├── channelInitializer
│ │ │ └── ServerInitializer.java
│ │ └── handler
│ │ │ └── ApiServerHandler.java
│ └── websocket
│ │ ├── WebsocketServer.java
│ │ ├── channelInitializer
│ │ └── WebSocketChannelInitializer.java
│ │ └── handler
│ │ └── TextWebsocketFrameHandler.java
│ └── utils
│ ├── CodeWrapperUtil.java
│ ├── DockerConfig.java
│ └── UserConfig.java
├── resource
└── META-INF
│ └── MANIFEST.MF
└── resources
├── com
└── runcode
│ └── mapper
│ └── CodeSegmentMapper.xml
└── logback.xml
/.classpath:
--------------------------------------------------------------------------------
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 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | out/
2 | .idea/
3 | .settings/
4 | target/
5 | .project
6 | db.properties
7 | docker.properties
8 | user.properties
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM java:8
2 | MAINTAINER Rhett
3 | WORKDIR /data
4 | COPY ./runcodeNettyWithJava_jar/* /data/
5 | COPY ./runcodeNettyWithJava_jar/config/* /data/config/
6 | COPY ./certs/* /data/certs/
7 | EXPOSE 7000 7001
8 | CMD java -Xbootclasspath/a:./*:./config -jar runcodeNettyWithJava.jar
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [yyyy] [name of copyright owner]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # 介绍
2 |
3 | 该项目是一个在线运行代码项目的服务端应用,使用Java语言开发,提供WebSocket服务和HTTP服务,支持前后端分离,因此你可以自己写前端。运行代码需要Docker环境,因此你需要安装Docker,并且开放远程连接,详见下方。
4 |
5 | [成品效果](https://run.codetool.top/)
6 |
7 | # 支持功能
8 |
9 | ## 在线运行代码(Websocket服务)
10 |
11 | 用户前端通过Websocket和后端服务器连接,将编程语言类型、源代码(目前仅支持单文件)传到服务器,服务器交给Docker运行,并将执行过程中实时产生的输出回显给客户端。
12 |
13 | 
14 |
15 | ### 保存代码(HTTP服务)
16 |
17 | 保存代码的目的是记录一段代码到数据库、并返回一个插入数据的id,客户端可以根据这个ID来生成一个页面,当访问这个页面的时候查询数据库,将保存的代码显示出来。
18 |
19 | **该功能以RestAPI的形式实现,用Netty同时实现了一个HTTP服务器,目前仅支持持有固定密码的人员提交保存请求,尚未做用户身份鉴别相关功能**
20 |
21 | 
22 |
23 | 对应的,当其他用户访问这个页面,就能从数据库取出代码,并显示在网页上:
24 |
25 | 例如:
26 |
27 | https://run.codetool.top/?id=34
28 |
29 | 
30 |
31 | # 支持在线运行的编程语言
32 |
33 | 目前支持语言:
34 |
35 | - [x] Java
36 | - [x] C++/C(共用G++)
37 | - [x] Golang
38 | - [x] Python3
39 |
40 | 有望加入语言
41 |
42 | - [ ] NodeJS
43 | - [ ] PHP
44 | - [ ] ...
45 |
46 | # 想直接用成品的请看
47 |
48 | 有意使用该项目的,我就默认你是个站长了,搭建服务端应用会比较麻烦,因此对于这部分有疑问的可以直接来联系作者或发Issue。
49 |
50 | 运行该项目建议持有两台服务器,一台用于运行Netty服务和数据库,一台用于提供Docker服务。(放到同一台上也可以)
51 |
52 | 在[下载页面](https://github.com/codeband-top/RunCodeOnline/releases)下载`release*.zip`,解压到本地。
53 |
54 | ## 建立数据库
55 |
56 | 使用根目录下的`code_segment.sql`文件在Netty服务器上创建一个数据库,用于存代码。
57 |
58 | *TODO:*
59 | 有时间了把这个也搞成Docker镜像
60 |
61 | ## 开启Docker远程连接
62 |
63 | Docker必须开启远程连接,按下面这篇博客进行操作:
64 |
65 | https://blog.csdn.net/yaofeng_hyy/article/details/80923941
66 |
67 | **得到的证书文件下载到本地,替换掉`certs/`下面的文件**
68 |
69 | 你还需要提前拉取用于运行代码的镜像,例如对于目前支持的编程语言:
70 |
71 | ```shell
72 | docker pull openjdk:11
73 | docker pull gcc:7.3
74 | docker pull python:3
75 | docker pull golang:1.14
76 | ```
77 |
78 | ## 修改配置文件
79 |
80 | 修改`runcodeNettyWithJava_jar/config`中的以下文件:
81 |
82 | `user.properties`:
83 |
84 | ```properties
85 | # 前端保存代码到数据库用于验证的密码
86 | password = 123456
87 | # websocket服务器的端口
88 | ws_server_port = 7000
89 | # http服务器的端口
90 | api_server_port = 7001
91 | ```
92 |
93 | `db.properties`:
94 |
95 | ```properties
96 | driver=com.mysql.cj.jdbc.Driver
97 |
98 | # 修改为自己的数据库连接信息
99 | url=jdbc:mysql://localhost:3306/runcode?useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true
100 | username=root
101 | password=123456
102 | ```
103 |
104 | `docker.properties`:
105 |
106 | ```properties
107 | # docker连接信息
108 | dockerHost = tcp://localhost:2375
109 | # 不用改,我写死在Dockerfile中了
110 | dockerCertPath = /data/certs
111 | # 填写自己的信息(意义不大)
112 | registryUsername = user
113 | registryPassword = 123456
114 | registryEmail =
115 | ```
116 |
117 | 可选修改`logback.xml`配置,用于输出日志。
118 |
119 | 将该文件夹上传到用于运行Netty服务的服务器,通过下面命令生成Docker镜像:
120 |
121 | ```shell
122 | docker build -t runcode:1.0 .
123 | ```
124 |
125 | 通过下面命令生成Docker容器并后台开始运行:
126 |
127 | ```shell
128 | docker run -d --name=runcode -p 7000:7000 -p 7001:7001 runcode:1.0
129 | ```
130 |
131 | 服务器必须开放7000和7001端口(如果使用反向代理可以不需要,并且强烈建议使用反向代理将这两个服务代理到80或443端口)
132 |
133 | Nginx反向代理配置:(可选)
134 |
135 | ```conf
136 | # 代理HTTP服务
137 | location /
138 | {
139 | proxy_pass http://localhost:7001;
140 | proxy_set_header Host localhost;
141 | proxy_set_header X-Real-IP $remote_addr;
142 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
143 | proxy_set_header REMOTE-HOST $remote_addr;
144 |
145 | add_header Cache-Control no-cache;
146 | }
147 |
148 | # 代理WebSocket服务
149 | location /runcode {
150 | proxy_pass http://localhost:7000;
151 | proxy_http_version 1.1;
152 | proxy_set_header Upgrade $http_upgrade;
153 | proxy_set_header Connection "upgrade";
154 | }
155 | ```
156 |
157 | 尝试访问你搭建的HTTP服务(例如:http://localhost:7001/ ),若有输出,说明HTTP服务运行成功。否则可以通过`docker logs runcode`查看程序日志输出。
158 |
159 | # API使用说明
160 |
161 | ## Websocket服务
162 |
163 | ### Websocket路径:`/runcode`
164 |
165 | 客户端发送的数据格式:
166 |
167 | ```json
168 | {
169 | "langType": "cpp/java/python3/golang",
170 | "content": "需要运行的源码"
171 | }
172 | ```
173 |
174 | 服务端响应的数据:
175 |
176 | 程序的实时运行输出,纯文本
177 |
178 | ## HTTP服务
179 |
180 | ### `GET /codeSegment/{id}`
181 |
182 | 根据id获取一段保存的代码,服务端返回数据格式:
183 |
184 | ```json
185 | {
186 | "success": true/false,
187 | "message": "提示信息",
188 | "data":{
189 | "lang": "cpp/java/python3/golang",
190 | "content": "保存的源码"
191 | }
192 | }
193 | ```
194 |
195 | 若根据id未查到信息,则`success`为false,`data`内不包含数据。
196 |
197 | ### `POST /codeSegment`
198 |
199 | 保存一段代码,请求体格式:
200 |
201 | ```json
202 | {
203 | "langType": "cpp/java/python3/golang",
204 | "content": "需要保存的源码",
205 | "password": "配置的密码"
206 | }
207 | ```
208 |
209 | 响应格式:
210 |
211 | ```json
212 | {
213 | "success": true/false,
214 | "message": "提示信息",
215 | "data":{
216 | "id": Number
217 | }
218 | }
219 | ```
220 |
221 | # 有意参与源码编写的请看
222 |
223 | ## 技术点
224 |
225 | 前端技术点(有点拉跨,应该会重写):
226 |
227 | + CodeMirror代码编辑器
228 | + BootStrap
229 | + JQuery
230 |
231 | 后端技术点:
232 |
233 | + Docker
234 | + Netty(实现WebSocket服务器和HTTP服务器)
235 | + [docker-java](https://github.com/docker-java/docker-java)
236 |
237 | ## 后端项目结构
238 |
239 | 
240 |
241 | ## 待完善列表
242 |
243 | - [ ] 有谁能帮我做个好看点的前端吗请迅速联系我(๑•̀ㅂ•́)و✧ 现在的前端太拉跨了
244 | - [ ] 编程语言相关信息的枚举可以通过读取配置文件的形式完成,更方便拓展
245 | - [ ] 前端控制器和`TextWebsocketFrameHandler`中路由分发部分代码耦合度较高,可以改进
246 | - [ ] 可以加入更多和数据库有关的功能,例如更新代码、加入用户功能等,不过用Netty写HTTP服务会比较挑战,要求具备较高的底层手写能力
247 | - [ ] ...
248 |
--------------------------------------------------------------------------------
/code_segment.sql:
--------------------------------------------------------------------------------
1 | CREATE DATABASE runcode;
2 |
3 | USE runcode;
4 |
5 | CREATE TABLE `code_segment` (
6 | `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
7 | `code_content` varchar(4000) CHARACTER SET utf8mb4 NOT NULL,
8 | `code_type` varchar(20) NOT NULL,
9 | `gmt_create` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
10 | PRIMARY KEY (`id`)
11 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
--------------------------------------------------------------------------------
/config/db-default.properties:
--------------------------------------------------------------------------------
1 | driver=com.mysql.cj.jdbc.Driver
2 |
3 | # 修改为自己的数据库连接信息
4 | url=jdbc:mysql://localhost:3306/runcode?useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true
5 | username=root
6 | password=123456
--------------------------------------------------------------------------------
/config/docker-default.properties:
--------------------------------------------------------------------------------
1 | # docker连接信息
2 | dockerHost = tcp://localhost:2375
3 | # docker连接验证证书在服务器上的存放地址
4 | dockerCertPath = /data/certs
5 | # 填写自己的信息(意义不大)
6 | registryUsername = user
7 | registryPassword = 123456
8 | registryEmail =
--------------------------------------------------------------------------------
/config/mybatis-config.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/config/user-default.properties:
--------------------------------------------------------------------------------
1 | # 客户端提交保存代码的请求时用于身份验证的密码
2 | password = 123456
3 | # Websocket服务器端口
4 | ws_server_port = 7000
5 | # HTTP服务器端口
6 | api_server_port = 7001
7 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 4.0.0
6 |
7 | org.example
8 | runcodeNettyWithJava
9 | 1.0-SNAPSHOT
10 |
11 | UTF-8
12 | 1.8
13 | 1.8
14 | true
15 |
16 |
17 |
18 | io.netty
19 | netty-all
20 | 4.1.50.Final
21 |
22 |
23 | org.projectlombok
24 | lombok
25 | 1.18.12
26 |
27 |
28 |
29 | cn.hutool
30 | hutool-all
31 | 5.1.0
32 |
33 |
34 |
35 | com.github.docker-java
36 | docker-java
37 | 3.2.1
38 |
39 |
40 | junit
41 | junit
42 | 4.13.1
43 |
44 |
45 |
46 |
47 | org.slf4j
48 | slf4j-api
49 | 1.7.30
50 |
51 |
52 |
53 |
54 | ch.qos.logback
55 | logback-classic
56 | 1.2.3
57 |
58 |
59 |
60 | org.mybatis
61 | mybatis
62 | 3.5.4
63 |
64 |
65 |
66 | mysql
67 | mysql-connector-java
68 | 8.0.19
69 |
70 |
71 |
72 |
73 |
--------------------------------------------------------------------------------
/runcodeNetty.iml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/main/java/com/runcode/ServerBootStrap.java:
--------------------------------------------------------------------------------
1 | package com.runcode;
2 |
3 | import com.runcode.server.http.RestApiServer;
4 | import com.runcode.server.http.handler.ApiServerHandler;
5 | import com.runcode.server.websocket.WebsocketServer;
6 |
7 | import java.util.concurrent.*;
8 |
9 | /**
10 | * 服务器启动类
11 | * @author RhettPeng
12 | */
13 | public class ServerBootStrap {
14 |
15 | public static void main(String[] args) {
16 | ExecutorService executorService = new ThreadPoolExecutor(2, 2, 0, TimeUnit.MICROSECONDS,new SynchronousQueue<>());
17 | // 启动websocket服务器
18 | executorService.execute(()->{
19 | try {
20 | new WebsocketServer().startup();
21 | } catch (Exception e) {
22 | e.printStackTrace();
23 | }
24 | });
25 | // 启动http服务器
26 | executorService.execute(()->{
27 | try {
28 | new RestApiServer().startup();
29 | } catch (InterruptedException e) {
30 | e.printStackTrace();
31 | }
32 | });
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/main/java/com/runcode/controller/CodeSegmentController.java:
--------------------------------------------------------------------------------
1 | package com.runcode.controller;
2 |
3 | import cn.hutool.json.JSONUtil;
4 | import com.runcode.entities.CodeSegmentPO;
5 | import com.runcode.entities.Result;
6 | import com.runcode.entities.SaveCodeRequest;
7 | import com.runcode.mapper.CodeSegmentMapper;
8 | import com.runcode.utils.UserConfig;
9 | import io.netty.handler.codec.http.FullHttpRequest;
10 | import io.netty.handler.codec.http.HttpMethod;
11 | import io.netty.util.CharsetUtil;
12 | import org.apache.ibatis.io.Resources;
13 | import org.apache.ibatis.session.SqlSession;
14 | import org.apache.ibatis.session.SqlSessionFactory;
15 | import org.apache.ibatis.session.SqlSessionFactoryBuilder;
16 | import org.junit.Ignore;
17 |
18 | import java.io.FileInputStream;
19 | import java.io.IOException;
20 | import java.io.InputStream;
21 | import java.util.regex.Matcher;
22 | import java.util.regex.Pattern;
23 |
24 | /**
25 | * 处理前端Rest请求
26 | * @author RhettPeng
27 | */
28 | public class CodeSegmentController {
29 | private CodeSegmentMapper codeSegmentMapper;
30 | private final Pattern pattern = Pattern.compile("/codeSegment/(\\d+)");
31 | private static final String API_PATH = "/codeSegment";
32 |
33 | /**
34 | * 初始化Mapper
35 | */
36 | public CodeSegmentController(){
37 | // Mybatis配置
38 | InputStream mybatisConfig = null;
39 | try {
40 | // 初始化mapper
41 | mybatisConfig = new FileInputStream("config/mybatis-config.xml");
42 | SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
43 | SqlSessionFactory factory = builder.build(mybatisConfig);
44 | SqlSession session = factory.openSession(true);
45 | codeSegmentMapper = session.getMapper(CodeSegmentMapper.class);
46 | } catch (IOException exception) {
47 | exception.printStackTrace();
48 | }
49 | }
50 |
51 | /**
52 | * 处理前端请求
53 | * @param request
54 | * @return
55 | */
56 | @Ignore
57 | public Result handle(FullHttpRequest request){
58 | if(request.method() == HttpMethod.GET){
59 | // 使用正则表达式匹配请求
60 | Matcher matcher = pattern.matcher(request.uri());
61 | if(matcher.matches()){
62 | // 获取路径中的id
63 | String id = matcher.group(1);
64 | return getById(Long.valueOf(id));
65 | }
66 | }else if(request.method() == HttpMethod.POST){
67 | if(API_PATH.equals(request.uri())){
68 | SaveCodeRequest saveCodeRequest = JSONUtil.toBean(request.content().toString(CharsetUtil.UTF_8), SaveCodeRequest.class);
69 | if(!UserConfig.PASSWORD.equals(saveCodeRequest.getPassword())){
70 | return Result.ERROR().msg("密码错误!");
71 | }
72 | return save(saveCodeRequest);
73 | }
74 | }
75 | return Result.ERROR();
76 | }
77 |
78 | /**
79 | * 根据id查询代码
80 | * @param id
81 | * @return
82 | */
83 | public Result getById(Long id){
84 | CodeSegmentPO codeSegment = codeSegmentMapper.getById(id);
85 | if(codeSegment == null) {
86 | return Result.ERROR();
87 | }
88 | return Result.OK()
89 | .data("lang", codeSegment.getCodeType())
90 | .data("content",codeSegment.getCodeContent());
91 | }
92 |
93 | /**
94 | * 保存一段代码
95 | * @param saveCodeRequest
96 | * @return
97 | */
98 | public Result save(SaveCodeRequest saveCodeRequest){
99 | CodeSegmentPO codeSegment = new CodeSegmentPO();
100 | codeSegment.setCodeContent(saveCodeRequest.getContent());
101 | codeSegment.setCodeType(saveCodeRequest.getLangType());
102 | codeSegmentMapper.save(codeSegment);
103 | long id = codeSegment.getId();
104 | return Result.OK().data("id", id);
105 | }
106 | }
107 |
--------------------------------------------------------------------------------
/src/main/java/com/runcode/docker/DockerJavaClient.java:
--------------------------------------------------------------------------------
1 | package com.runcode.docker;
2 |
3 | import com.github.dockerjava.api.DockerClient;
4 | import com.github.dockerjava.api.command.CreateContainerResponse;
5 | import com.github.dockerjava.api.command.DockerCmdExecFactory;
6 | import com.github.dockerjava.api.command.ExecCreateCmdResponse;
7 | import com.github.dockerjava.core.DefaultDockerClientConfig;
8 | import com.github.dockerjava.core.DockerClientBuilder;
9 | import com.github.dockerjava.core.DockerClientConfig;
10 | import com.github.dockerjava.core.command.ExecStartResultCallback;
11 | import com.github.dockerjava.jaxrs.JerseyDockerCmdExecFactory;
12 | import com.runcode.entities.CodeLang;
13 | import com.runcode.utils.DockerConfig;
14 | import io.netty.channel.ChannelHandlerContext;
15 | import lombok.extern.slf4j.Slf4j;
16 |
17 | import java.io.*;
18 | import java.util.PropertyResourceBundle;
19 | import java.util.ResourceBundle;
20 | import java.util.concurrent.TimeUnit;
21 |
22 | /**
23 | * 控制Docker的Java客户端
24 | * @author RhettPeng
25 | */
26 | @Slf4j
27 | public class DockerJavaClient {
28 | /**
29 | * 计数器,用于给容器名取后缀
30 | */
31 | private static int counter = 0;
32 |
33 |
34 | /**
35 | * 获取一个docker连接
36 | * @return
37 | */
38 | public DockerClient getDockerClient(){
39 | DockerCmdExecFactory dockerCmdExecFactory = new JerseyDockerCmdExecFactory().withReadTimeout(20000)
40 | .withConnectTimeout(2000);
41 | DockerClientConfig config = null;
42 |
43 | config = DefaultDockerClientConfig.createDefaultConfigBuilder()
44 | .withDockerHost(DockerConfig.DOCKER_HOST)
45 | .withDockerTlsVerify(true)
46 | .withDockerCertPath(DockerConfig.DOCKER_CERT_PATH)
47 | .withDockerConfig(DockerConfig.DOCKER_CERT_PATH)
48 | .withRegistryUrl("https://index.docker.io/v1/")
49 | .withRegistryUsername(DockerConfig.REGISTRY_USER_NAME)
50 | .withRegistryPassword(DockerConfig.REGISTRY_PASSWORD)
51 | .withRegistryEmail(DockerConfig.REGISTRY_EMAIL)
52 | .build();
53 |
54 | return DockerClientBuilder.getInstance(config)
55 | .withDockerCmdExecFactory(dockerCmdExecFactory).build();
56 | }
57 |
58 | /**
59 | * 创建运行代码的容器
60 | * @param dockerClient
61 | * @param langType
62 | * @return
63 | */
64 | private String createContainer(DockerClient dockerClient,CodeLang langType){
65 | // 创建容器请求
66 | CreateContainerResponse containerResponse = dockerClient.createContainerCmd(langType.getImageName())
67 | .withName(langType.getContainerNamePrefix()+counter)
68 | .withWorkingDir(DockerConfig.DOCKER_CONTAINER_WORK_DIR)
69 | .withStdinOpen(true)
70 | .exec();
71 |
72 | return containerResponse.getId();
73 | }
74 |
75 | /**
76 | * 将程序代码写入容器中的一个文件
77 | * @param dockerClient
78 | * @param containerId
79 | * @param langType
80 | * @param sourcecode
81 | * @return
82 | * @throws InterruptedException
83 | */
84 | private String writeFileToContainer(DockerClient dockerClient,String containerId,CodeLang langType,String sourcecode) throws InterruptedException {
85 | String workDir = DockerConfig.DOCKER_CONTAINER_WORK_DIR;
86 | String fileName = langType.getFileName();
87 | String path = workDir + "/" + fileName;
88 |
89 | // 将\替换为\\\\,转义反斜杠
90 | sourcecode = sourcecode.replaceAll("\\\\", "\\\\\\\\\\\\\\\\");
91 |
92 | // 将"替换为\",转义引号
93 | sourcecode = sourcecode.replaceAll("\\\"", "\\\\\"");
94 |
95 | // 通过重定向符写入文件,注意必须要带前面两个参数,否则重定向符会失效,和Docker CMD的机制有关
96 | ExecCreateCmdResponse createCmdResponse = dockerClient.execCreateCmd(containerId)
97 | .withCmd("/bin/sh","-c", "echo \""+sourcecode+"\" > "+path)
98 | .exec();
99 | dockerClient.execStartCmd(createCmdResponse.getId())
100 | .exec(new ExecStartResultCallback(System.out,System.err))
101 | .awaitCompletion();
102 | return fileName;
103 | }
104 |
105 | /**
106 | * 在容器上EXEC一条CMD命令
107 | * @param dockerClient docker客户端
108 | * @param command 命令,EXEC数组
109 | * @param containerId 容器ID
110 | * @param timeout 超时时间(单位为秒)
111 | * @param ctx
112 | * @param isFinal 是否是最后一条指令
113 | * @throws InterruptedException
114 | */
115 | private void runCommandOnContainer(DockerClient dockerClient, String[] command, String containerId,
116 | int timeout,ChannelHandlerContext ctx,boolean isFinal) throws InterruptedException {
117 | ExecCreateCmdResponse createCmdResponse = dockerClient.execCreateCmd(containerId)
118 | .withAttachStdout(true)
119 | .withAttachStderr(true)
120 | .withCmd(command)
121 | .exec();
122 | dockerClient.execStartCmd(createCmdResponse.getId())
123 | .exec(new RunCodeResultCallback(ctx,isFinal))
124 | //.awaitCompletion();
125 | .awaitCompletion(timeout,TimeUnit.SECONDS);
126 | }
127 |
128 | /**
129 | * 执行一个程序
130 | * @param langType 编程语言类型
131 | * @param sourcecode 源代码
132 | * @throws InterruptedException
133 | * @throws IOException
134 | */
135 | public void exec(CodeLang langType, String sourcecode, ChannelHandlerContext ctx){
136 | DockerClient dockerClient = getDockerClient();
137 | // 计数器加一
138 | counter++;
139 | log.info("开始创建容器");
140 | // 创建容器
141 | String containerId = createContainer(dockerClient, langType);
142 | log.info("创建容器结束");
143 | log.info("开始运行容器");
144 | // 运行容器
145 | dockerClient.startContainerCmd(containerId).exec();
146 |
147 | try {
148 | log.info("开始写文件");
149 | writeFileToContainer(dockerClient, containerId, langType, sourcecode);
150 | log.info("写文件结束");
151 | String[][] commands = langType.getExecCommand(langType.getFileName());
152 | for(int i = 0;i {
16 |
17 | /**
18 | * websocket客户端的channel
19 | */
20 | private ChannelHandlerContext ctx;
21 | /**
22 | * 是否是程序执行的最后一条指令
23 | */
24 | private boolean isFinal;
25 | /**
26 | * 开始执行的时间戳
27 | */
28 | private long startTime;
29 |
30 | public RunCodeResultCallback(ChannelHandlerContext ctx,boolean isFinal) {
31 | this.ctx = ctx;
32 | this.isFinal = isFinal;
33 | if(isFinal) {
34 | startTime = System.currentTimeMillis();
35 | }
36 | }
37 |
38 | /**
39 | * 接收到一条Docker响应后,返回给Websocket客户端
40 | * @param frame
41 | */
42 | @Override
43 | public void onNext(Frame frame) {
44 | log.info("收到docker响应");
45 | if (frame != null) {
46 | String msg = new String(frame.getPayload());
47 | switch (frame.getStreamType()) {
48 | case STDOUT:
49 | case RAW:
50 | case STDERR:
51 | ctx.channel().writeAndFlush(new TextWebSocketFrame(msg));
52 | break;
53 | default:
54 | break;
55 | }
56 | }
57 | }
58 |
59 | /**
60 | * 若当前指令执行完成,则发送最后一条消息
61 | */
62 | @Override
63 | public void onComplete() {
64 | if(isFinal){
65 | long endTime = System.currentTimeMillis();
66 | ctx.channel().writeAndFlush(new TextWebSocketFrame("程序运行结束,总耗费时间:"+(endTime-startTime)/1000.0+"s\n"));
67 | log.info("程序运行结束,发送最后一帧");
68 | }
69 | super.onComplete();
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/src/main/java/com/runcode/entities/CodeLang.java:
--------------------------------------------------------------------------------
1 | package com.runcode.entities;
2 |
3 | import cn.hutool.core.util.IdUtil;
4 |
5 | /**
6 | * 各种编程语言的枚举
7 | * @author RhettPeng
8 | */
9 |
10 | public enum CodeLang {
11 | /**
12 | * Python语言
13 | */
14 | PYTHON3{
15 | @Override
16 | public String getImageName() {
17 | return "python:3";
18 | }
19 |
20 | @Override
21 | public String getContainerNamePrefix() {
22 | return "python-running-script-";
23 | }
24 |
25 | @Override
26 | public String[][] getExecCommand(String fileName) {
27 | return new String[][]{{"python",fileName}};
28 | }
29 |
30 | @Override
31 | public String getFileName() {
32 | return "temp.py";
33 | }
34 | },
35 | /**
36 | * C++语言
37 | */
38 | CPP{
39 | @Override
40 | public String getImageName() {
41 | return "gcc:7.3";
42 | }
43 |
44 | @Override
45 | public String getContainerNamePrefix() {
46 | return "cpp-running-file-";
47 | }
48 |
49 | @Override
50 | public String[][] getExecCommand(String fileName) {
51 | return new String[][]{{"g++",fileName,"-o","temp"},{"./temp"}};
52 | }
53 |
54 | @Override
55 | public String getFileName() {
56 | return "temp.cpp";
57 | }
58 | },
59 | /**
60 | * JAVA语言
61 | */
62 | JAVA{
63 | @Override
64 | public String getImageName() {
65 | return "openjdk:11";
66 | }
67 |
68 | @Override
69 | public String getContainerNamePrefix() {
70 | return "java-running-file-";
71 | }
72 |
73 | @Override
74 | public String[][] getExecCommand(String fileName) {
75 | // jdk11可以不经过javac
76 | return new String[][]{{"java",fileName}};
77 | }
78 |
79 | @Override
80 | public String getFileName() {
81 | return "Untitled.java";
82 | }
83 |
84 | },
85 | /**
86 | * Go语言
87 | */
88 | GOLANG{
89 | @Override
90 | public String getImageName() {
91 | return "golang:1.14";
92 | }
93 |
94 | @Override
95 | public String getContainerNamePrefix() {
96 | return "golang-running-file-";
97 | }
98 |
99 | @Override
100 | public String[][] getExecCommand(String fileName) {
101 | // Go可不经过编译
102 | return new String[][]{{"go","run",fileName}};
103 | }
104 |
105 | @Override
106 | public String getFileName() {
107 | return "temp.go";
108 | }
109 |
110 | };
111 |
112 | /**
113 | * 镜像名称
114 | */
115 | public String getImageName(){
116 | return null;
117 | }
118 |
119 | /**
120 | * 容器名称前缀
121 | */
122 | public String getContainerNamePrefix(){
123 | return null;
124 | }
125 |
126 | /**
127 | * 执行该文件需要的指令,供Docker EXEC调用
128 | */
129 | public String[][] getExecCommand(String fileName){
130 | return null;
131 | }
132 |
133 | /**
134 | * 该编程语言的文件名
135 | */
136 | public String getFileName(){return null;}
137 | }
138 |
--------------------------------------------------------------------------------
/src/main/java/com/runcode/entities/CodeSegmentPO.java:
--------------------------------------------------------------------------------
1 | package com.runcode.entities;
2 |
3 | import lombok.Data;
4 |
5 | import java.util.Date;
6 |
7 | /**
8 | * 数据库存储的对象
9 | * @author RhettPeng
10 | */
11 | @Data
12 | public class CodeSegmentPO {
13 | public Long id;
14 | /**
15 | * 源代码
16 | */
17 | public String codeContent;
18 | /**
19 | * 编程语言类型
20 | */
21 | public String codeType;
22 | /**
23 | * 创建时间
24 | */
25 | public Date gmtCreate;
26 | }
27 |
--------------------------------------------------------------------------------
/src/main/java/com/runcode/entities/CodeWrapperDTO.java:
--------------------------------------------------------------------------------
1 | package com.runcode.entities;
2 |
3 | import lombok.Data;
4 |
5 | /**
6 | * 前端发来的websocket请求
7 | * @author RhettPeng
8 | */
9 | @Data
10 | public class CodeWrapperDTO {
11 | /**
12 | * 编程语言类型
13 | */
14 | private String langType;
15 | /**
16 | * 代码内容
17 | */
18 | private String content;
19 | }
20 |
--------------------------------------------------------------------------------
/src/main/java/com/runcode/entities/Result.java:
--------------------------------------------------------------------------------
1 | package com.runcode.entities;
2 |
3 | import cn.hutool.json.JSONUtil;
4 |
5 | import java.util.HashMap;
6 | import java.util.Map;
7 |
8 |
9 | /**
10 | * HTTP RestApi响应
11 | * @author RhettPeng
12 | */
13 | public class Result {
14 | /**
15 | * 是否成功
16 | */
17 | private boolean success;
18 |
19 | /**
20 | * 返回消息
21 | */
22 | private String message;
23 |
24 | /**
25 | * 返回信息
26 | */
27 | private Map data = new HashMap<>();
28 |
29 | private Result() {}
30 |
31 | public static Result OK(){
32 | Result result = new Result();
33 | result.success = true;
34 | result.message = "成功!";
35 | return result;
36 | }
37 |
38 | public static Result ERROR(){
39 | Result result = new Result();
40 | result.success = false;
41 | result.message = "失败!";
42 | return result;
43 | }
44 |
45 | public Result data(String key,Object value){
46 | this.data.put(key, value);
47 | return this;
48 | }
49 |
50 | public Result msg(String msg){
51 | this.message = msg;
52 | return this;
53 | }
54 |
55 | public boolean isSuccess() {
56 | return success;
57 | }
58 |
59 | public void setSuccess(boolean success) {
60 | this.success = success;
61 | }
62 |
63 | public String getMessage() {
64 | return message;
65 | }
66 |
67 | public void setMessage(String message) {
68 | this.message = message;
69 | }
70 |
71 | public Map getData() {
72 | return data;
73 | }
74 |
75 | public void setData(Map data) {
76 | this.data = data;
77 | }
78 |
79 | public String toJson(){
80 | return JSONUtil.parse(this).toStringPretty();
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/src/main/java/com/runcode/entities/SaveCodeRequest.java:
--------------------------------------------------------------------------------
1 | package com.runcode.entities;
2 |
3 | import lombok.Data;
4 |
5 | /**
6 | * 保存代码请求
7 | * @author RhettPeng
8 | */
9 | @Data
10 | public class SaveCodeRequest {
11 |
12 | /**
13 | * 编程语言类型
14 | */
15 | private String langType;
16 | /**
17 | * 代码内容
18 | */
19 | private String content;
20 |
21 | /**
22 | * 密码
23 | */
24 | private String password;
25 | }
26 |
--------------------------------------------------------------------------------
/src/main/java/com/runcode/mapper/CodeSegmentMapper.java:
--------------------------------------------------------------------------------
1 | package com.runcode.mapper;
2 |
3 | import com.runcode.entities.CodeSegmentPO;
4 |
5 | /**
6 | * @author RhettPeng
7 | */
8 | public interface CodeSegmentMapper {
9 | /**
10 | * 通过id查找一段代码
11 | * @param id 数据库中的id
12 | * @return CodeSegmentPO对象
13 | */
14 | CodeSegmentPO getById(Long id);
15 |
16 | /**
17 | * 保存到数据库
18 | * @param codeSegment 代码段对象
19 | * @return 插入数据的主键值
20 | */
21 | Long save(CodeSegmentPO codeSegment);
22 | }
23 |
--------------------------------------------------------------------------------
/src/main/java/com/runcode/server/http/RestApiServer.java:
--------------------------------------------------------------------------------
1 | package com.runcode.server.http;
2 |
3 | import com.runcode.server.http.channelInitializer.ServerInitializer;
4 | import com.runcode.utils.UserConfig;
5 | import io.netty.bootstrap.ServerBootstrap;
6 | import io.netty.channel.Channel;
7 | import io.netty.channel.ChannelOption;
8 | import io.netty.channel.EventLoopGroup;
9 | import io.netty.channel.nio.NioEventLoopGroup;
10 | import io.netty.channel.socket.nio.NioServerSocketChannel;
11 | import lombok.extern.slf4j.Slf4j;
12 |
13 | import java.io.IOException;
14 | import java.io.InputStream;
15 | import java.util.PropertyResourceBundle;
16 | import java.util.ResourceBundle;
17 |
18 | /**
19 | * API服务器
20 | * @author RhettPeng
21 | */
22 | @Slf4j
23 | public class RestApiServer {
24 |
25 | public void startup() throws InterruptedException {
26 | EventLoopGroup bossGroup = new NioEventLoopGroup(1);
27 | EventLoopGroup workerGroup = new NioEventLoopGroup();
28 | try {
29 | ServerBootstrap b = new ServerBootstrap();
30 | b.option(ChannelOption.SO_BACKLOG, 1024);
31 | b.group(bossGroup, workerGroup)
32 | .channel(NioServerSocketChannel.class)
33 | .childHandler(new ServerInitializer());
34 |
35 | Channel ch = b.bind(UserConfig.API_PORT).sync().channel();
36 | log.info("---------http服务器正在启动---------");
37 |
38 | ch.closeFuture().sync();
39 | } finally {
40 | bossGroup.shutdownGracefully();
41 | workerGroup.shutdownGracefully();
42 | }
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/main/java/com/runcode/server/http/channelInitializer/ServerInitializer.java:
--------------------------------------------------------------------------------
1 | package com.runcode.server.http.channelInitializer;
2 |
3 | import com.google.protobuf.Api;
4 | import com.runcode.server.http.handler.ApiServerHandler;
5 | import io.netty.channel.ChannelHandler;
6 | import io.netty.channel.ChannelInitializer;
7 | import io.netty.channel.ChannelPipeline;
8 | import io.netty.channel.socket.SocketChannel;
9 | import io.netty.handler.codec.http.HttpObjectAggregator;
10 | import io.netty.handler.codec.http.HttpServerCodec;
11 |
12 | /**
13 | * HTTP服务器通道初始化
14 | * @author RhettPeng
15 | */
16 | public class ServerInitializer extends ChannelInitializer {
17 | @Override
18 | protected void initChannel(SocketChannel ch) throws Exception {
19 | ChannelPipeline p = ch.pipeline();
20 | /*HTTP 服务的解码器*/
21 | p.addLast(new HttpServerCodec());
22 | /*HTTP 消息的合并处理*/
23 | p.addLast(new HttpObjectAggregator(2048));
24 | p.addLast(new ApiServerHandler());
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/main/java/com/runcode/server/http/handler/ApiServerHandler.java:
--------------------------------------------------------------------------------
1 | package com.runcode.server.http.handler;
2 |
3 | import cn.hutool.json.JSONObject;
4 | import com.runcode.controller.CodeSegmentController;
5 | import com.runcode.entities.Result;
6 | import io.netty.buffer.ByteBuf;
7 | import io.netty.buffer.Unpooled;
8 | import io.netty.channel.ChannelFutureListener;
9 | import io.netty.channel.ChannelHandlerContext;
10 | import io.netty.channel.ChannelInboundHandlerAdapter;
11 | import io.netty.handler.codec.http.*;
12 | import io.netty.util.AsciiString;
13 | import io.netty.util.CharsetUtil;
14 |
15 | import static io.netty.handler.codec.http.HttpResponseStatus.*;
16 | import static io.netty.handler.codec.http.HttpVersion.*;
17 |
18 |
19 | /**
20 | * @author RhettPeng
21 | */
22 | public class ApiServerHandler extends ChannelInboundHandlerAdapter {
23 |
24 | private static final AsciiString CONTENT_TYPE = new AsciiString("Content-Type");
25 | private static final AsciiString CONTENT_LENGTH = new AsciiString("Content-Length");
26 | private static final AsciiString CONNECTION = new AsciiString("Connection");
27 | private static final AsciiString KEEP_ALIVE = new AsciiString("keep-alive");
28 |
29 | @Override
30 | public void channelReadComplete(ChannelHandlerContext ctx) {
31 | ctx.flush();
32 | }
33 |
34 | @Override
35 | public void channelRead(ChannelHandlerContext ctx, Object msg) {
36 |
37 | if (msg instanceof FullHttpRequest) {
38 | //客户端的请求对象
39 | FullHttpRequest req = (FullHttpRequest) msg;
40 |
41 | //获取客户端的URL
42 | String uri = req.uri();
43 |
44 | //根据不同的请求API做不同的处理(路由分发)
45 | if(req.uri().startsWith("/codeSegment"))
46 | {
47 | Result result = new CodeSegmentController().handle(req);
48 | responseResult(ctx,req,result);
49 | }else {
50 | responseResult(ctx,req,Result.ERROR().msg("请求路径出错!"));
51 | }
52 |
53 | }
54 | }
55 |
56 | /**
57 | * 响应HTTP的请求
58 | * @param ctx
59 | * @param req
60 | * @param result
61 | */
62 | private void responseResult(ChannelHandlerContext ctx, FullHttpRequest req , Result result)
63 | {
64 | boolean keepAlive = HttpUtil.isKeepAlive(req);
65 | String json = result.toJson();
66 | byte[] jsonByteByte = result.toJson().getBytes();
67 |
68 | FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, OK, Unpooled.wrappedBuffer(jsonByteByte));
69 | response.headers().set(CONTENT_TYPE, "application/json;charset=utf-8");
70 | response.headers().setInt(CONTENT_LENGTH, response.content().readableBytes());
71 | response.headers().set("Access-Control-Allow-Origin", "*");
72 | response.headers().set("Access-Control-Allow-Headers", "*");
73 |
74 | /* HTTP/1.1 持久化相关 */
75 | if (!keepAlive) {
76 | ctx.write(response).addListener(ChannelFutureListener.CLOSE);
77 | } else {
78 | response.headers().set(CONNECTION, KEEP_ALIVE);
79 | ctx.write(response);
80 | }
81 | }
82 |
83 | @Override
84 | public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
85 | cause.printStackTrace();
86 | ctx.close();
87 | }
88 |
89 | /**
90 | * 获取请求的内容
91 | * @param request
92 | * @return
93 | */
94 | private String parseJsonRequest(FullHttpRequest request) {
95 | ByteBuf jsonBuf = request.content();
96 | String jsonStr = jsonBuf.toString(CharsetUtil.UTF_8);
97 | return jsonStr;
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/src/main/java/com/runcode/server/websocket/WebsocketServer.java:
--------------------------------------------------------------------------------
1 | package com.runcode.server.websocket;
2 |
3 | import com.runcode.server.websocket.channelInitializer.WebSocketChannelInitializer;
4 | import com.runcode.utils.UserConfig;
5 | import io.netty.bootstrap.ServerBootstrap;
6 | import io.netty.channel.*;
7 | import io.netty.channel.nio.NioEventLoopGroup;
8 | import io.netty.channel.socket.nio.NioServerSocketChannel;
9 | import lombok.extern.slf4j.Slf4j;
10 |
11 | import java.io.IOException;
12 | import java.io.InputStream;
13 | import java.util.PropertyResourceBundle;
14 | import java.util.ResourceBundle;
15 |
16 | /**
17 | * websocket服务器
18 | * @author RhettPeng
19 | */
20 | @Slf4j
21 | public class WebsocketServer {
22 |
23 | /**
24 | * 绑定端口并启动服务器
25 | * @param port
26 | * @throws Exception
27 | */
28 | public void startup() throws Exception{
29 | //配置服务器的NIO线程组
30 | EventLoopGroup bossGroup = new NioEventLoopGroup();
31 | EventLoopGroup workerGroup = new NioEventLoopGroup();
32 | try{
33 | ServerBootstrap serverBootstrap = new ServerBootstrap();
34 | serverBootstrap.group(bossGroup,workerGroup)
35 | .channel(NioServerSocketChannel.class)
36 | .option(ChannelOption.SO_BACKLOG, 1024)
37 | .childHandler(new WebSocketChannelInitializer());
38 | log.info("---------websocket服务器正在启动---------");
39 | ChannelFuture future = serverBootstrap.bind(UserConfig.WS_PORT).sync();
40 | //等待服务端监听端口关闭
41 | future.channel().closeFuture().sync();
42 | }finally {
43 | bossGroup.shutdownGracefully();
44 | workerGroup.shutdownGracefully();
45 | }
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/main/java/com/runcode/server/websocket/channelInitializer/WebSocketChannelInitializer.java:
--------------------------------------------------------------------------------
1 | package com.runcode.server.websocket.channelInitializer;
2 |
3 | import com.runcode.server.websocket.handler.TextWebsocketFrameHandler;
4 | import io.netty.channel.ChannelInitializer;
5 | import io.netty.channel.ChannelPipeline;
6 | import io.netty.channel.socket.SocketChannel;
7 | import io.netty.handler.codec.http.HttpObjectAggregator;
8 | import io.netty.handler.codec.http.HttpServerCodec;
9 | import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;
10 | import io.netty.handler.stream.ChunkedWriteHandler;
11 |
12 | /**
13 | * @author RhettPeng
14 | */
15 | public class WebSocketChannelInitializer extends ChannelInitializer {
16 |
17 |
18 | @Override
19 | protected void initChannel(SocketChannel ch) throws Exception {
20 |
21 | //向管道加入处理器
22 | //得到管道
23 | ChannelPipeline pipeline = ch.pipeline();
24 | //基于HTTP的编解码器
25 | pipeline.addLast(new HttpServerCodec());
26 | //以块方式传输数据
27 | pipeline.addLast(new ChunkedWriteHandler());
28 | //HTTP数据在传输过程中是分段,HttpObjectAggregator可以将多个段聚合
29 | pipeline.addLast(new HttpObjectAggregator(1024));
30 | //将http协议升级为websocket协议,参数代表请求的uri
31 | pipeline.addLast(new WebSocketServerProtocolHandler("/runcode"));
32 | pipeline.addLast(new TextWebsocketFrameHandler());
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/main/java/com/runcode/server/websocket/handler/TextWebsocketFrameHandler.java:
--------------------------------------------------------------------------------
1 | package com.runcode.server.websocket.handler;
2 |
3 | import com.runcode.docker.DockerJavaClient;
4 | import com.runcode.entities.CodeLang;
5 | import com.runcode.entities.CodeWrapperDTO;
6 | import com.runcode.utils.CodeWrapperUtil;
7 | import io.netty.channel.ChannelHandlerContext;
8 | import io.netty.channel.SimpleChannelInboundHandler;
9 | import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
10 | import lombok.extern.slf4j.Slf4j;
11 |
12 | /**
13 | * Websocket帧处理器
14 | * @author RhettPeng
15 | */
16 | @Slf4j
17 | public class TextWebsocketFrameHandler extends SimpleChannelInboundHandler {
18 |
19 |
20 | private DockerJavaClient dockerJavaClient = new DockerJavaClient();
21 |
22 | /**
23 | * 读取Websocket客户端发来的信息
24 | * @param ctx
25 | * @param msg
26 | * @throws Exception
27 | */
28 | @Override
29 | protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception {
30 | try{
31 | CodeWrapperDTO codeWrapper = CodeWrapperUtil.fromJson(msg.text());
32 | log.info("收到客户端信息:"+msg.text());
33 | dockerJavaClient.exec(CodeLang.valueOf(codeWrapper.getLangType().toUpperCase()),codeWrapper.getContent(),ctx);
34 | }catch (Exception e){
35 | log.warn("执行过程中出现异常:");
36 | e.printStackTrace();
37 | ctx.channel().writeAndFlush(new TextWebSocketFrame("发生意外,运行出错!"));
38 | }
39 | }
40 |
41 |
42 | /**
43 | * 建立连接
44 | * @param ctx
45 | * @throws Exception
46 | */
47 | @Override
48 | public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
49 | log.info("与客户端建立连接");
50 | }
51 |
52 | /**
53 | * 连接关闭
54 | * @param ctx
55 | * @throws Exception
56 | */
57 | @Override
58 | public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
59 | log.info("与客户端断开连接");
60 | }
61 |
62 |
63 | /**
64 | * 捕获异常
65 | * @param ctx
66 | * @param cause
67 | * @throws Exception
68 | */
69 | @Override
70 | public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
71 | log.error("异常发生"+cause.getMessage());
72 | ctx.close();//关闭连接
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/src/main/java/com/runcode/utils/CodeWrapperUtil.java:
--------------------------------------------------------------------------------
1 | package com.runcode.utils;
2 |
3 | import cn.hutool.json.JSONUtil;
4 | import com.runcode.entities.CodeWrapperDTO;
5 |
6 | /**
7 | * @author RhettPeng
8 | */
9 | public class CodeWrapperUtil {
10 | public static CodeWrapperDTO fromJson(String jsonObject){
11 | return JSONUtil.toBean(jsonObject, CodeWrapperDTO.class);
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/main/java/com/runcode/utils/DockerConfig.java:
--------------------------------------------------------------------------------
1 | package com.runcode.utils;
2 |
3 | import java.io.FileInputStream;
4 | import java.io.IOException;
5 | import java.io.InputStream;
6 | import java.util.PropertyResourceBundle;
7 | import java.util.ResourceBundle;
8 |
9 | /**
10 | * @author RhettPeng
11 | */
12 | public class DockerConfig {
13 | public static String DOCKER_HOST;
14 | public static String DOCKER_CERT_PATH;
15 | public static String REGISTRY_USER_NAME;
16 | public static String REGISTRY_PASSWORD;
17 | public static String REGISTRY_EMAIL;
18 |
19 | public static final String DOCKER_CONTAINER_WORK_DIR = "/usr/src/myapp";
20 |
21 | private static final String DOCKER_SETTING_FILE_NAME = "config/docker.properties";
22 |
23 | static{
24 | // 用户配置
25 | InputStream in = null;
26 | try {
27 | in = new FileInputStream(DOCKER_SETTING_FILE_NAME);
28 | ResourceBundle rb = new PropertyResourceBundle(in);
29 | DOCKER_HOST = rb.getString("dockerHost");
30 | DOCKER_CERT_PATH = rb.getString("dockerCertPath");
31 | REGISTRY_USER_NAME = rb.getString("registryUsername");
32 | REGISTRY_PASSWORD = rb.getString("registryPassword");
33 | REGISTRY_EMAIL = rb.getString("registryEmail");
34 | } catch (IOException e) {
35 | e.printStackTrace();
36 | }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/main/java/com/runcode/utils/UserConfig.java:
--------------------------------------------------------------------------------
1 | package com.runcode.utils;
2 |
3 | import java.io.FileInputStream;
4 | import java.io.FileNotFoundException;
5 | import java.io.IOException;
6 | import java.io.InputStream;
7 | import java.util.PropertyResourceBundle;
8 | import java.util.ResourceBundle;
9 |
10 | /**
11 | * @author RhettPeng
12 | */
13 | public class UserConfig {
14 | public static String PASSWORD;
15 | public static Integer WS_PORT;
16 | public static Integer API_PORT;
17 | private static final String USER_SETTING_FILE_NAME = "config/user.properties";
18 |
19 | static{
20 | // 用户配置
21 | InputStream in = null;
22 | try {
23 | in = new FileInputStream(USER_SETTING_FILE_NAME);
24 | ResourceBundle rb = new PropertyResourceBundle(in);
25 | PASSWORD = rb.getString("password");
26 | WS_PORT = Integer.parseInt(rb.getString("ws_server_port"));
27 | API_PORT = Integer.parseInt(rb.getString("api_server_port"));
28 | } catch (IOException e) {
29 | e.printStackTrace();
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/main/resource/META-INF/MANIFEST.MF:
--------------------------------------------------------------------------------
1 | Manifest-Version: 1.0
2 | Main-Class: com.runcode.ServerBootStrap
3 | Class-Path: netty-codec-socks-4.1.46.Final.jar httpclient-4.5.12.jar gua
4 | va-19.0.jar jakarta.ws.rs-api-2.1.6.jar docker-java-core-3.2.1.jar hk2-
5 | api-2.6.1.jar netty-codec-4.1.46.Final.jar jersey-apache-connector-2.30
6 | .1.jar aopalliance-repackaged-2.6.1.jar netty-common-4.1.46.Final.jar n
7 | etty-transport-4.1.46.Final.jar netty-handler-proxy-4.1.46.Final.jar lo
8 | gback-core-1.2.3.jar jakarta.activation-1.2.1.jar jackson-core-2.10.3.j
9 | ar commons-compress-1.20.jar jackson-databind-2.10.3.jar netty-transpor
10 | t-native-unix-common-4.1.46.Final.jar netty-transport-native-kqueue-4.1
11 | .46.Final-osx-x86_64.jar osgi-resource-locator-1.0.3.jar jakarta.xml.bi
12 | nd-api-2.3.2.jar javassist-3.25.0-GA.jar docker-java-transport-jersey-3
13 | .2.1.jar bcprov-jdk15on-1.64.jar netty-transport-native-epoll-4.1.46.Fi
14 | nal-linux-x86_64.jar lombok-1.18.12.jar junixsocket-native-common-2.3.2
15 | .jar bcpkix-jdk15on-1.64.jar netty-handler-4.1.46.Final.jar jackson-mod
16 | ule-jaxb-annotations-2.10.3.jar junit-4.13.jar hk2-utils-2.6.1.jar comm
17 | ons-lang-2.6.jar commons-io-2.6.jar jackson-jaxrs-base-2.10.3.jar jacks
18 | on-jaxrs-json-provider-2.10.3.jar httpcore-4.4.13.jar hamcrest-core-1.3
19 | .jar mysql-connector-java-8.0.19.jar docker-java-transport-netty-3.2.1.
20 | jar hutool-all-5.1.0.jar slf4j-api-1.7.30.jar junixsocket-common-2.3.2.
21 | jar jackson-annotations-2.10.3.jar jersey-common-2.30.1.jar netty-resol
22 | ver-4.1.46.Final.jar mybatis-3.5.4.jar hk2-locator-2.6.1.jar netty-code
23 | c-http-4.1.46.Final.jar jakarta.inject-2.6.1.jar jersey-client-2.30.1.j
24 | ar netty-buffer-4.1.46.Final.jar docker-java-api-3.2.1.jar logback-clas
25 | sic-1.2.3.jar jersey-hk2-2.30.1.jar protobuf-java-3.6.1.jar jakarta.ann
26 | otation-api-1.3.5.jar netty-all-4.1.50.Final.jar commons-codec-1.11.jar
27 | jakarta.activation-api-1.2.1.jar docker-java-3.2.1.jar jcl-over-slf4j-
28 | 1.7.30.jar
29 |
30 |
--------------------------------------------------------------------------------
/src/main/resources/com/runcode/mapper/CodeSegmentMapper.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
19 |
20 |
21 | insert into code_segment(code_content,code_type) values
22 | (#{codeContent},#{codeType})
23 |
24 |
--------------------------------------------------------------------------------
/src/main/resources/logback.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | System.err
10 |
11 |
12 | ${pattern}
13 |
14 |
15 |
16 |
17 |
18 |
19 | ${log_dir}/logback.log
20 |
21 |
22 | ${pattern}
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------