Skip to content
This repository was archived by the owner on Jul 10, 2025. It is now read-only.

Commit 388ac82

Browse files
committed
Initial add
Signed-off-by: Matt Butcher <matt.butcher@microsoft.com>
0 parents  commit 388ac82

File tree

6 files changed

+202
-0
lines changed

6 files changed

+202
-0
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
/*.wasm
2+
target/
3+
_scratch/

LICENSE.txt

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
The MIT License (MIT)
2+
3+
Copyright (c) Microsoft Corporation. All rights reserved.
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE

Makefile

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# These are for both `run` (implicit) and `test` (explicit)
2+
PATH_INFO ?= /static/fileserver.gr
3+
X_MATCHED_ROUTE ?= /static/...
4+
5+
.PHONY: run
6+
run:
7+
PATH_INFO=${PATH_INFO} X_MATCHED_ROUTE=${X_MATCHED_ROUTE} \
8+
grain fileserver.gr
9+
10+
.PHONY: build
11+
build:
12+
grain compile fileserver.gr
13+
14+
.PHONY: test
15+
test:build
16+
test:
17+
wasmtime --dir . --env PATH_INFO=${PATH_INFO} \
18+
--env X_MATCHED_ROUTE=${X_MATCHED_ROUTE} \
19+
fileserver.gr.wasm

README.md

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
# Fileserver-gr
2+
3+
A [Wagi](https://github.com/deislabs/wagi) static fileserver written in Grain.
4+
5+
> DeisLabs is experimenting with many WASM technologies right now.
6+
> This is one of a multitude of projects designed to test the limits
7+
> of WebAssembly as a cloud-based runtime. This code is not stable or
8+
> production ready.
9+
10+
## Building and Running Locally
11+
12+
You will need the [Grain](https://grain-lang.org) toolkit to work with this repo.
13+
14+
To run:
15+
16+
```console
17+
$ make run
18+
```
19+
20+
To compile:
21+
22+
```console
23+
$ make build
24+
```
25+
26+
To run test (Wasmtime required):
27+
28+
```
29+
$ make test
30+
```
31+
32+
## Running in Wagi
33+
34+
Here is an example `modules.toml` for [Wagi](https://github.com/deislabs/wagi):
35+
36+
```toml
37+
[[module]]
38+
route = "/static/..."
39+
module = "/path/to/fileserver/fileserver.gr.wasm"
40+
volumes = {"/" = "/path/to/fileserver"}
41+
```
42+
43+
The above configures Wagi to map the path `/static/...` to the `fileserver.gr.wasm` module. Then it serves all of the files in this project.
44+
45+
Assuming you have Wagi running on `http://localhost:3000`, you can then run this command:
46+
47+
```console
48+
$ curl -v localhost:3000/static/fileserver.gr
49+
* Trying 127.0.0.1...
50+
* TCP_NODELAY set
51+
* Connected to localhost (127.0.0.1) port 3000 (#0)
52+
> GET /static/fileserver.gr HTTP/1.1
53+
> Host: localhost:3000
54+
> User-Agent: curl/7.64.1
55+
> Accept: */*
56+
>
57+
< HTTP/1.1 200 OK
58+
< content-type: text/plain
59+
< content-length: 1522
60+
< date: Thu, 03 Jun 2021 16:44:05 GMT
61+
<
62+
// This is a simple Wagi static file server.
63+
64+
import Env from "./env"
65+
import Map from "map"
66+
// The rest of the source of fileserver.gr
67+
```
68+
69+
The fileserver took `/static/filserver.gr`, removed the `/static/` part from the front, and then loaded `fileserver.gr` from the directory mounted in the `modules.toml`. Note that any subdirectories are also served. So `/static/foo/bar` would translate to the path `foo/bar` inside of the WebAssembly module (which in the example above would fully resolve to "/path/to/fileserver/foo/bar").

env.gr

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import Process from "sys/process"
2+
import Map from "map"
3+
import Array from "array"
4+
import String from "string"
5+
import Option from "option"
6+
7+
// Split an environment variable at the first equals sign.
8+
let splitEnvVar = (item) => {
9+
let offsetOpt = String.indexOf("=", item)
10+
11+
// For now, fail if the env var is malformed.
12+
let offset = Option.unwrap(offsetOpt)
13+
14+
let key = String.slice(0, offset, item)
15+
let val = String.slice(offset+1, String.length(item), item)
16+
(key, val)
17+
}
18+
19+
export let envMap = () => {
20+
let parsed = Map.make()
21+
let env = Process.env()
22+
let pairs = Array.map(splitEnvVar,env)
23+
24+
Array.forEach((item) => {
25+
let (k, v) = item
26+
Map.set(k, v, parsed)
27+
}, pairs)
28+
parsed
29+
}
30+
31+

fileserver.gr

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
// This is a simple Wagi static file server.
2+
3+
import Env from "./env"
4+
import Map from "map"
5+
import Option from "option"
6+
import File from "sys/file"
7+
import String from "string"
8+
9+
let serve = (path) => {
10+
File.fdWrite(File.stderr, "Loading file ")
11+
File.fdWrite(File.stderr, path)
12+
File.fdWrite(File.stderr, "\n")
13+
14+
// Open file
15+
// TODO: This throws an error when something goes wrong.
16+
// But Grain 0.3 does not have a try/catch yet.
17+
// When we figure out how, we need to catch the error here
18+
// and do a 404 or 500.
19+
let input = File.pathOpen(
20+
File.pwdfd,
21+
[],
22+
path,
23+
[],
24+
[File.FdRead],
25+
[],
26+
[],
27+
)
28+
29+
File.fdWrite(File.stdout, "Content-Type: text/plain\n\n")
30+
31+
// Pipe output to STDOUT
32+
let rec pipe = (in, out) => {
33+
let (d, len) = File.fdRead(in, 1024)
34+
File.fdWrite(out, d)
35+
if (len > 0) {
36+
pipe(in, out)
37+
}
38+
}
39+
pipe(input, File.stdout)
40+
File.fdClose(input)
41+
}
42+
43+
let guestpath = (env) => {
44+
let req = Option.unwrap(Map.get("PATH_INFO", env))
45+
let mut base = Option.unwrap(Map.get("X_MATCHED_ROUTE", env))
46+
let dots = String.indexOf("/...", base)
47+
if (Option.isSome(dots) ) {
48+
base = String.slice(0, Option.unwrap(dots), base)
49+
}
50+
String.slice(String.length(base) + 1, String.length(req), req)
51+
}
52+
53+
let notFound = () => {
54+
File.fdWrite(File.stdout, "Status: 404\n\nNot Found")
55+
}
56+
57+
let kv = Env.envMap()
58+
let pathInfo = guestpath(kv)
59+
serve(pathInfo)

0 commit comments

Comments
 (0)