Skip to content

Commit 465125a

Browse files
committed
1158: Add backend for AWS JShell
1 parent f49d430 commit 465125a

File tree

16 files changed

+897
-0
lines changed

16 files changed

+897
-0
lines changed

jshell-aws-backend/README.md

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
# JShell AWS Backend
2+
This module contains the infrastructure and code to create an AWS Lambda hosted JShell API.
3+
The API is used to evaluate Java code in AWS using JShell.
4+
5+
Sample request that can be made to this API:
6+
7+
```curl
8+
curl -X POST "https://<FUNCTION_ID>.lambda-url.<AWS_REGION>>.on.aws/" \
9+
-H "Content-Type: application/json" \
10+
-d '{"code": "System.out.println(\"Hello, World\");"}'
11+
```
12+
13+
## Getting Started
14+
To use this project in your own AWS account, you first need an AWS account and an [AWS authenticated CLI](https://docs.aws.amazon.com/cli/v1/userguide/cli-chap-authentication.html).
15+
16+
### Required CLI tools
17+
* [AWS ClI](https://aws.amazon.com/cli/)
18+
* [SAM CLI](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/install-sam-cli.html)
19+
* [cfn-lint](https://github.com/aws-cloudformation/cfn-lint) (this is for CloudFormation linting)
20+
21+
Once your terminal is all setup and authenticated, the easiest way to deploy this application is by running the `./deploy.sh`.
22+
This will compile and upload the project to AWS, it will then create a stack called "jshell" in your AWS account. The Lambda URL will be printed to the console.
23+
24+
If you prefer to deploy without the help of the `./deploy.sh` you can do it manually:
25+
1. `cd infrastructure`
26+
2. `sam build`
27+
3. `sam deploy`
28+
29+
To delete the stack, this can be done either in CloudFormation via the AWS website or using the CLI command:
30+
```bash
31+
aws cloudformation delete-stack --stack-name jshell
32+
```
33+
34+
## Testing Locally
35+
To test the Lambda locally without the need to use an AWS account you can use the SAM Local to invoke the Lambda directly.
36+
37+
First build the project:
38+
1. `cd infrastructure`
39+
2. `sam build`
40+
41+
Now you can directly send events to the Lambda e.g.
42+
```bash
43+
echo '{"body": "{\"code\": \"System.out.println(\\\"Hello, World!\\\");\"}"}' | sam local invoke "CodeRunnerFunction" -e -
44+
```
45+
We pass `body` in this request unlike how we do in AWS because we need to match how AWS would send the request to the function.
46+
47+
**Note:** This requires [Docker](https://www.docker.com/) to be installed on your machine.
48+
49+
If you want to test locally using a web server (e.g. testing the integration with TJ Bot) you can run the `start-local.sh`
50+
51+
This will spin up a web server locally. To test you can use the following cURL:
52+
```curl
53+
curl -X POST http://127.0.0.1:3000/jshell \
54+
-H "Content-Type: application/json" \
55+
-d '{"code": "System.out.println(\"Hello, World!\");"}'
56+
```
57+
**Note:** This is using the SAM CLI web server, and it can be very slow to serve requests.
58+
This also requires [Docker](https://www.docker.com/) to be installed on your machine.

jshell-aws-backend/build.gradle

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
plugins {
2+
id 'java'
3+
}
4+
5+
group = 'org.togetherjava'
6+
version = '1.0'
7+
8+
repositories {
9+
mavenCentral()
10+
}
11+
12+
dependencies {
13+
implementation 'com.amazonaws:aws-lambda-java-core:1.2.3'
14+
implementation 'com.amazonaws:aws-lambda-java-events:3.13.0'
15+
16+
implementation 'com.fasterxml.jackson.core:jackson-core:2.17.2'
17+
implementation 'com.fasterxml.jackson.core:jackson-annotations:2.17.2'
18+
implementation 'com.fasterxml.jackson.core:jackson-databind:2.17.2'
19+
20+
implementation 'org.apache.logging.log4j:log4j-api:2.23.1'
21+
implementation 'org.apache.logging.log4j:log4j-core:2.23.1'
22+
23+
testImplementation platform('org.junit:junit-bom:5.10.3')
24+
testImplementation 'org.junit.jupiter:junit-jupiter'
25+
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
26+
testImplementation 'org.mockito:mockito-core:5.12.0'
27+
}
28+
29+
test {
30+
useJUnitPlatform()
31+
testLogging {
32+
events "passed", "skipped", "failed"
33+
}
34+
}
35+
36+
sourceSets {
37+
test {
38+
java {
39+
srcDirs 'src/test/integration', 'src/test/unit'
40+
}
41+
}
42+
}

jshell-aws-backend/deploy.sh

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
#!/usr/bin/env bash
2+
3+
cfn-lint -t infrastructure/template.yaml
4+
sam validate -t infrastructure/template.yaml --region eu-west-2
5+
sam validate -t infrastructure/template.yaml --lint
6+
sam build -t infrastructure/template.yaml --parallel
7+
8+
sam deploy --stack-name "jshell" \
9+
--no-fail-on-empty-changeset \
10+
--no-confirm-changeset \
11+
--resolve-s3 \
12+
--s3-prefix "jshell" \
13+
--region "${AWS_REGION:-eu-west-2}" \
14+
--capabilities CAPABILITY_IAM \
15+
--template infrastructure/template.yaml
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
version = 0.1
2+
[default.deploy.parameters]
3+
stack_name = "jshell"
4+
resolve_s3 = true
5+
s3_prefix = "jshell"
6+
region = "eu-west-2"
7+
capabilities = "CAPABILITY_IAM"
8+
image_repositories = []
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
AWSTemplateFormatVersion: '2010-09-09'
2+
Description: JShell Stack
3+
Transform: [ AWS::LanguageExtensions, AWS::Serverless-2016-10-31 ]
4+
5+
Globals:
6+
Function:
7+
Timeout: 20
8+
CodeUri: ..
9+
MemorySize: 512
10+
Runtime: java21
11+
Architectures: [ arm64 ]
12+
13+
Resources:
14+
CodeRunnerFunction:
15+
Type: AWS::Serverless::Function
16+
Properties:
17+
Handler: org.togetherjava.jshell.CodeRunner::handleRequest
18+
SnapStart:
19+
ApplyOn: PublishedVersions
20+
AutoPublishAlias: live
21+
FunctionUrlConfig:
22+
AuthType: NONE
23+
Cors:
24+
AllowOrigins:
25+
- '*'
26+
Policies:
27+
- AWSLambdaBasicExecutionRole
28+
- Statement:
29+
Effect: Allow
30+
Action: lambda:InvokeFunctionUrl
31+
Resource: '*'
32+
Events:
33+
Api:
34+
Type: Api
35+
Properties:
36+
Path: /jshell
37+
Method: POST
38+
39+
CodeRunnerFunctionLogGroup:
40+
Type: AWS::Logs::LogGroup
41+
Properties:
42+
LogGroupName: !Sub /aws/lambda/${CodeRunnerFunction}
43+
RetentionInDays: 7
44+
45+
Outputs:
46+
LambdaURL:
47+
Value: !GetAtt CodeRunnerFunctionUrl.FunctionUrl

jshell-aws-backend/openapi_spec.yaml

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
openapi: 3.0.1
2+
info:
3+
title: "JShell API"
4+
version: "1.0"
5+
6+
servers:
7+
- url: https://33ykaxnvin5lusynogiwrevqgm0ovara.lambda-url.eu-west-2.on.aws/
8+
description: AWS
9+
- url: http://127.0.0.1:3000/jshell
10+
description: localhost
11+
12+
paths:
13+
/:
14+
post:
15+
requestBody:
16+
required: true
17+
content:
18+
application/json:
19+
schema:
20+
type: object
21+
properties:
22+
code:
23+
type: string
24+
example: "System.out.println(\"Hello, World!\");"
25+
responses:
26+
200:
27+
description: "Successful invocation of the JShell API"
28+
content:
29+
application/json:
30+
examples:
31+
Happy path:
32+
value:
33+
errorStream: ""
34+
outputStream: "Hello, World!"
35+
events: [
36+
statement: "System.out.println(\"Hello, World!\");",
37+
value: "",
38+
status: "VALID"
39+
]
40+
Syntax errors:
41+
value:
42+
errorStream: ""
43+
outputStream: ""
44+
events: [
45+
statement: "System.out.prinn(\"Hello, World!\");",
46+
value: null,
47+
status: "REJECTED"
48+
]
49+
System.err.println:
50+
value:
51+
errorStream: "Hello, World!"
52+
outputStream: ""
53+
events: [
54+
statement: "System.out.prinn(\"Hello, World!\");",
55+
value: "",
56+
status: "VALID"
57+
]
58+
Simple evaluation that provides a value:
59+
value:
60+
errorStream: ""
61+
outputStream: ""
62+
events: [
63+
statement: "1+1",
64+
value: "2",
65+
status: "VALID"
66+
]
67+
408:
68+
description: "Execution took too long so it timed out"
69+
content:
70+
application/json:
71+
examples:
72+
Response:
73+
value:
74+
error: "Execution timed out"
75+
400:
76+
description: "Missing fields or the provided input is incorrect"
77+
content:
78+
application/json:
79+
examples:
80+
Invalid request payload:
81+
value:
82+
error: "Invalid input format"
83+
Invalid code value:
84+
value:
85+
error: "Code field is empty or invalid"
86+
500:
87+
description: "Internal server error"
88+
content:
89+
application/json:
90+
examples:
91+
JShell exceptions:
92+
value:
93+
error: "Error during code execution"
94+
InputStream error:
95+
value:
96+
error: "Failed to read the request stream"

0 commit comments

Comments
 (0)