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

Feat: v3 .NET #144

Merged
merged 16 commits into from
Jun 8, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Next Next commit
Implement v3 in dotnet
  • Loading branch information
Meldiron committed Jan 30, 2023
commit 15701c6262f43e2759985076df2dd78bc04b7489
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,7 @@ env:
SERVER_PROCESS="DotNetRuntime.dll"
IMAGE=openruntimes/dotnet:${VERSION}-6.0
ARCH=linux/amd64,linux/arm64
TEST_SCRIPT=tests.sh
TEST_SCRIPT=tests-v3.sh


notifications:
Expand Down
4 changes: 2 additions & 2 deletions runtimes/dotnet-6.0/.env
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
INTERNAL_RUNTIME_KEY=secret-key
INTERNAL_RUNTIME_ENTRYPOINT=Index.cs
OPEN_RUNTIMES_SECRET=secret-key
OPEN_RUNTIMES_ENTRYPOINT=Index.cs
31 changes: 10 additions & 21 deletions runtimes/dotnet-6.0/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,25 +12,25 @@ To learn more about runtimes, visit [Structure](https://github.com/open-runtimes

```bash
mkdir dotnet-or && cd dotnet-or
printf "public async Task<RuntimeResponse> Main(RuntimeRequest req, RuntimeResponse res) => res.Json(new() {{ \"n\", new System.Random().NextDouble() }} );" > Index.cs
printf "public async Task<RuntimeOutput> Main(RuntimeContext context) => context.res.Json(new() {{ \"n\", new System.Random().NextDouble() }} );" > Index.cs
```

2. Build the code:

```bash
docker run -e INTERNAL_RUNTIME_ENTRYPOINT=Index.cs --rm --interactive --tty --volume $PWD:/usr/code openruntimes/dotnet:v3-6.0 sh /usr/local/src/build.sh
docker run -e OPEN_RUNTIMES_ENTRYPOINT=Index.cs --rm --interactive --tty --volume $PWD:/usr/code openruntimes/dotnet:v3-6.0 sh /usr/local/src/build.sh
```

3. Spin-up open-runtime:

```bash
docker run -p 3000:3000 -e INTERNAL_RUNTIME_KEY=secret-key -e INTERNAL_RUNTIME_ENTRYPOINT=Index.cs --rm --interactive --tty --volume $PWD/code.tar.gz:/tmp/code.tar.gz:ro openruntimes/dotnet:v3-6.0 sh /usr/local/src/start.sh
docker run -p 3000:3000 -e OPEN_RUNTIMES_SECRET=secret-key -e OPEN_RUNTIMES_ENTRYPOINT=Index.cs --rm --interactive --tty --volume $PWD/code.tar.gz:/tmp/code.tar.gz:ro openruntimes/dotnet:v3-6.0 sh /usr/local/src/start.sh
```

4. In new terminal window, execute function:

```bash
curl -H "X-Internal-Challenge: secret-key" -H "Content-Type: application/json" -X POST http:https://localhost:3000/ -d '{"payload": "{}"}'
curl -H "x-open-runtimes-secret: secret-key" -X GET http:https://localhost:3000/
```

Output `{"n":0.7232589496628183}` with random float will be displayed after the execution.
Expand Down Expand Up @@ -58,36 +58,25 @@ docker-compose up -d
4. Execute the function:

```bash
curl -H "X-Internal-Challenge: secret-key" -H "Content-Type: application/json" -X POST http:https://localhost:3000/ -d '{"payload": "{}"}'
curl -H "x-open-runtimes-secret: secret-key" -H "Content-Type: application/json" -X POST http:https://localhost:3000/ -d '{"id": "4"}'
```

You can now send `POST` request to `http:https://localhost:3000`. Make sure you have header `x-internal-challenge: secret-key`. If your function expects any parameters, you can pass an optional JSON body like so: `{ "payload":"{}" }`.
You can now send `POST` request to `http:https://localhost:3000`. Make sure you have header `x-open-runtimes-secret: secret-key`.

You can also make changes to the example code and apply the changes with the `docker-compose restart` command.

## Notes

- The `res` parameter has two methods:

- `Send()`: Send a string response to the client.
- `Json()`: Send a JSON response to the client.

You can respond with `Json()` by providing object:
- When writing function for this runtime, ensure it is named `Main`. An example of this is:

```cs
public async Task<RuntimeResponse> Main(RuntimeRequest req, RuntimeResponse res) =>
res.Json(new()
{
{ "message" , "Hello Open Runtimes 👋" },
{ "variables", req.Variables },
{ "headers", req.Headers },
{ "payload", req.Payload }
});
public async Task<RuntimeOutput> Main(RuntimeContext context) =>
context.res.send("Hello Open Runtimes 👋");
```

- To handle dependencies, you need to have `csproj` file containing the `PackageReferences` you desire. Dependencies will be automatically cached and installed, so you don't need to include the `.nuget` folder in your function.

- The default entrypoint is `Index.cs`. If your entrypoint differs, make sure to configure it using `INTERNAL_RUNTIME_ENTRYPOINT` environment variable, for instance, `INTERNAL_RUNTIME_ENTRYPOINT=src/App.cs`.
- The default entrypoint is `Index.cs`. If your entrypoint differs, make sure to configure it using `OPEN_RUNTIMES_ENTRYPOINT` environment variable, for instance, `OPEN_RUNTIMES_ENTRYPOINT=src/App.cs`.

## Authors

Expand Down
16 changes: 8 additions & 8 deletions runtimes/dotnet-6.0/build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,14 @@ build() {
done


case ${INTERNAL_RUNTIME_ENTRYPOINT##*.} in
case ${OPEN_RUNTIMES_ENTRYPOINT##*.} in
cs) write_cs_wrapper ;;
fs) write_fs_wrapper ;;
vb) write_vb_wrapper ;;
esac

# Remove the user code file (copy)
rm "${INTERNAL_RUNTIME_ENTRYPOINT}"
rm "${OPEN_RUNTIMES_ENTRYPOINT}"

# Build the executable
cd /usr/local/src
Expand All @@ -55,8 +55,8 @@ write_cs_wrapper() {
USINGS="${USINGS}${line}
"
esac
done < "$INTERNAL_RUNTIME_ENTRYPOINT"
CODE="$(sed /using*/d "$INTERNAL_RUNTIME_ENTRYPOINT")"
done < "$OPEN_RUNTIMES_ENTRYPOINT"
CODE="$(sed /using*/d "$OPEN_RUNTIMES_ENTRYPOINT")"

# Wrap the user code in a class
echo "${USINGS}
Expand All @@ -75,8 +75,8 @@ write_fs_wrapper() {
OPENS="${OPENS}${line}
"
esac
done < "$INTERNAL_RUNTIME_ENTRYPOINT"
CODE="$(sed /open*/d "$INTERNAL_RUNTIME_ENTRYPOINT" | sed 's/^/ /')"
done < "$OPEN_RUNTIMES_ENTRYPOINT"
CODE="$(sed /open*/d "$OPEN_RUNTIMES_ENTRYPOINT" | sed 's/^/ /')"
# Wrap the user code in a class
echo "namespace DotNetRuntime
${OPENS}
Expand All @@ -95,8 +95,8 @@ write_vb_wrapper() {
IMPORTS="${IMPORTS}${line}
"
esac
done < "$INTERNAL_RUNTIME_ENTRYPOINT"
CODE="$(sed /using*/d "$INTERNAL_RUNTIME_ENTRYPOINT")"
done < "$OPEN_RUNTIMES_ENTRYPOINT"
CODE="$(sed /using*/d "$OPEN_RUNTIMES_ENTRYPOINT")"

# Wrap the user code in a class
echo "${IMPORTS}
Expand Down
4 changes: 2 additions & 2 deletions runtimes/dotnet-6.0/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ services:
ports:
- 3000:3000
environment:
- INTERNAL_RUNTIME_KEY
- INTERNAL_RUNTIME_ENTRYPOINT
- OPEN_RUNTIMES_SECRET
- OPEN_RUNTIMES_ENTRYPOINT
volumes:
- ./example/:/usr/code:rw
command: sh -c "sh /usr/local/src/build.sh && cp /usr/code/code.tar.gz /tmp/code.tar.gz && sh /usr/local/src/start.sh"
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,20 @@

static readonly HttpClient http = new();

public async Task<RuntimeResponse> Main(RuntimeRequest req, RuntimeResponse res)
public async Task<RuntimeOutput> Main(RuntimeContext context)
{
string id = "1";
if (!string.IsNullOrEmpty(req.Payload))

if (!(context.req.body is String))
{
var payload = JsonConvert.DeserializeObject<Dictionary<string, object>>(req.Payload, settings: null);
id = payload?.TryGetValue("id", out var value) == true
? value.ToString()!
: "1";
Dictionary<string, object> body = (Dictionary<string, object>) context.req.body;
id = body.TryGetValue("id", out var value) ? (string) value : "1";
}

var response = await http.GetStringAsync($"https://jsonplaceholder.typicode.com/todos/{id}");
var todo = JsonConvert.DeserializeObject<Dictionary<string, object>>(response, settings: null);

return res.Json(new()
return context.res.json(new()
{
{ "message", "Hello Open Runtimes 👋" },
{ "todo", todo }
Expand Down
32 changes: 32 additions & 0 deletions runtimes/dotnet-6.0/src/CustomResponse.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
using System.Text;

namespace DotNetRuntime
{
class CustomResponse : IResult
{
private readonly string _body;
private readonly int _statusCode;
private readonly Dictionary<string, string> _headers;

public CustomResponse(string body, int statusCode, Dictionary<string, string>? headers = null)
{
if(headers == null) {
headers = new Dictionary<string,string>();
}

_body = body;
_statusCode = statusCode;
_headers = headers;
}

public Task ExecuteAsync(HttpContext httpContext)
{
string contentType = _headers.TryGetValue("content-type", out var contentTypeValue) ? (string) contentTypeValue : "plain/text";

httpContext.Response.StatusCode = _statusCode;
httpContext.Response.ContentType = contentType;
httpContext.Response.ContentLength = Encoding.UTF8.GetByteCount(_body);
return httpContext.Response.WriteAsync(_body);
}
}
}
115 changes: 67 additions & 48 deletions runtimes/dotnet-6.0/src/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,69 +3,88 @@
using System.IO;
using System.Text;
using System.Text.Json;
using Newtonsoft.Json;
using System.Web;

var app = WebApplication.Create(args);
app.Urls.Add("http:https://0.0.0.0:3000");
app.MapPost("/", Execute);
app.MapMethods("/{*path}", new[] { "GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS", "HEAD", "TRACE" }, Execute);
app.Run();

static async Task<IResult> Execute(
[FromHeader(Name = "x-internal-challenge")] string? challengeId,
[FromBody] RuntimeRequest? request)
static async Task<IResult> Execute(HttpRequest request)
{
if (challengeId is not null
&& challengeId != Environment.GetEnvironmentVariable("INTERNAL_RUNTIME_KEY"))
{
return Results.Problem(
detail: "Unauthorized",
statusCode: 500);
string secret = request.Headers.TryGetValue("x-open-runtimes-secret", out var secretValue) ? (string) secretValue : "";
if(secret != Environment.GetEnvironmentVariable("OPEN_RUNTIMES_SECRET")) {
return new CustomResponse("Unauthorized. Provide correct \"x-open-runtimes-secret\" header.", 500);
}

StreamReader reader = new StreamReader(request.Body);
string rawBody = await reader.ReadToEndAsync();
object body = rawBody;
Dictionary<string, string> headers = new Dictionary<string, string>();
string method = request.Method;
string url = request.Path + request.QueryString;

foreach (var entry in request.Headers) {
var header = entry.Key;
var value = entry.Value;
header = header.ToLower();

if(!(header.StartsWith("x-open-runtimes-"))) {
headers.Add(header, value);
}
}

var originalOut = Console.Out;
var originalErr = Console.Error;
var outString = new StringBuilder();
var errString = new StringBuilder();
string contentType = request.Headers.TryGetValue("content-type", out var contentTypeValue) ? (string) contentTypeValue : "";
if(contentType.Contains("application/json")) {
body = JsonConvert.DeserializeObject<Dictionary<string, object>>(rawBody);
}

RuntimeRequest contextRequest = new RuntimeRequest(rawBody, body, headers, method, url);
RuntimeResponse contextResponse = new RuntimeResponse();
RuntimeContext context = new RuntimeContext(contextRequest, contextResponse);

var customstd = new StringBuilder();
var customstdWriter = new StringWriter(customstd);
Console.SetOut(customstdWriter);
Console.SetError(customstdWriter);

RuntimeOutput? output = null;

try
{
var codeWrapper = new Wrapper();
var req = request ?? new();
var res = new RuntimeResponse();

var outWriter = new StringWriter(outString);
var errWriter = new StringWriter(errString);

Console.SetOut(outWriter);
Console.SetError(errWriter);

var response = await codeWrapper.Main(req, res);
var output = new Dictionary<string, object?>()
{
{ "response", response.Data },
{ "stdout", outString.ToString() }
};

return Results.Text(
content: JsonSerializer.Serialize(output),
contentType: "application/json",
contentEncoding: System.Text.Encoding.UTF8);
output = await codeWrapper.Main(context);
}
catch (Exception e)
{
Console.Error.Write(e);
var output = new Dictionary<string, object?>()
{
{ "stderr", errString.ToString() },
{ "stdout", outString.ToString() }
};
return Results.Text(
content: JsonSerializer.Serialize(output),
contentType: "application/json",
contentEncoding: System.Text.Encoding.UTF8);
context.error(e.ToString());
output = context.res.send("", 500, new Dictionary<string,string>());
}
finally
{
Console.SetOut(originalOut);
Console.SetError(originalErr);

if(output == null) {
context.error("Return statement missing. return context.res.empty() if no response is expected.");
output = context.res.send("", 500, new Dictionary<string,string>());
}

var outputHeaders = new Dictionary<string, string>();

foreach (var entry in output.headers) {
var header = entry.Key;
var value = entry.Value;
header = header.ToLower();

if(!(header.StartsWith("x-open-runtimes-"))) {
outputHeaders.Add(header, value);
}
}

if(String.IsNullOrEmpty(customstd.ToString())) {
context.log("Unsupported log noticed. Use context.log() or context.error() for logging.");
}

outputHeaders.Add("x-open-runtimes-logs", System.Web.HttpUtility.UrlEncode(String.Join('\n', context._logs)));
outputHeaders.Add("x-open-runtimes-errors", System.Web.HttpUtility.UrlEncode(String.Join('\n', context._errors)));

return new CustomResponse(output.body, output.statusCode, outputHeaders);
}
28 changes: 28 additions & 0 deletions runtimes/dotnet-6.0/src/RuntimeContext.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
using System.Collections;

namespace DotNetRuntime
{
public class RuntimeContext
{
public RuntimeRequest req { get; set; }
public RuntimeResponse res { get; set; }

public ArrayList _logs = new ArrayList();
public ArrayList _errors = new ArrayList();

public RuntimeContext(RuntimeRequest req, RuntimeResponse res)
{
this.req = req;
this.res = res;
}

public void log(object message) {
this._logs.Add(message.ToString());
}

public void error(object message) {
this._errors.Add(message.ToString());
}
}
}

17 changes: 17 additions & 0 deletions runtimes/dotnet-6.0/src/RuntimeOutput.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
namespace DotNetRuntime
{
public class RuntimeOutput
{
public string body { get; set; }
public int statusCode { get; set; }
public Dictionary<string, string> headers { get; set; }

public RuntimeOutput(string body, int statusCode, Dictionary<string, string> headers)
{
this.body = body;
this.statusCode = statusCode;
this.headers = headers;
}
}
}

Loading